From e3461dd5cdef14405a362c8c47aaa0075458e43c Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Wed, 29 Apr 2026 17:39:11 -0700 Subject: [PATCH] mobile v0.1 with deployment for ios (#191) * stage 1: project init * stage 2: skeleton with navigation * step 2.5: streams list * step 4: stream playback experience * step 5-6: compose experience * fix: broken record * transcode media particles to mp4 * build: reproducible go generate * build: rename skaffold module for particle processor worker * infra: increase particle processor worker resources Was dealing with OOM errors * tweaks to mobile * log transcode work * view on desktop placeholder * tweak padding * cap video resolution to save on memory * infra: bump memory limits as insurance * ux improvements * update bundle id for mobile * config for mobile --- ...orker.yml => deploy-particleprocessor.yml} | 2 +- go/Dockerfile.particleprocessorworker | 2 + go/cmd/particleprocessorworker/main.go | 149 + go/go.mod | 3 + go/go.sum | 2 + go/internal/billing/service.go | 2 +- go/internal/depot/models.go | 9 + go/internal/depot/service.go | 52 + go/internal/livestore/membershippublisher.go | 2 +- go/internal/particle/firestore_types.go | 12 +- .../particle/networkmembershipchecker.go | 2 +- go/internal/testhelper/mocks/aero/gen.go | 2 +- go/k8s/dev/particleprocessorworker.yaml | 8 +- go/k8s/prod/particleprocessorworker.yaml | 8 +- go/skaffold.yaml | 2 +- js/desktop/src/api/types.ts | 5 + .../particles/media-particle-view.tsx | 12 +- js/mobile/.gitignore | 10 + js/mobile/app.config.ts | 66 + js/mobile/assets/flowy.icns | Bin 0 -> 59660 bytes js/mobile/assets/flowy.ico | Bin 0 -> 6792 bytes .../assets/flowy.iconset/icon_128x128.png | Bin 0 -> 3133 bytes .../assets/flowy.iconset/icon_128x128@2x.png | Bin 0 -> 6796 bytes js/mobile/assets/flowy.iconset/icon_16x16.png | Bin 0 -> 667 bytes .../assets/flowy.iconset/icon_16x16@2x.png | Bin 0 -> 1001 bytes .../assets/flowy.iconset/icon_256x256.png | Bin 0 -> 6796 bytes .../assets/flowy.iconset/icon_256x256@2x.png | Bin 0 -> 19139 bytes js/mobile/assets/flowy.iconset/icon_32x32.png | Bin 0 -> 1001 bytes .../assets/flowy.iconset/icon_32x32@2x.png | Bin 0 -> 1693 bytes .../assets/flowy.iconset/icon_512x512.png | Bin 0 -> 19139 bytes js/mobile/assets/icon.png | Bin 0 -> 35911 bytes js/mobile/assets/icon_green.png | Bin 0 -> 36555 bytes js/mobile/assets/icon_red.png | Bin 0 -> 36464 bytes js/mobile/assets/images/FLOWY-4.svg | 3 + js/mobile/assets/sound.wav | Bin 0 -> 589902 bytes js/mobile/assets/sounds/click.mp3 | Bin 0 -> 33024 bytes js/mobile/babel.config.js | 10 + js/mobile/eas.json | 40 + js/mobile/global.css | 3 + js/mobile/index.ts | 5 + js/mobile/metro.config.js | 6 + js/mobile/nativewind-env.d.ts | 1 + js/mobile/package.json | 52 + js/mobile/src/App.tsx | 40 + js/mobile/src/api/client.ts | 270 + js/mobile/src/api/types.ts | 324 + js/mobile/src/components/Avatar.tsx | 60 + js/mobile/src/components/BottomSheet.tsx | 174 + .../src/components/ComposingIndicator.tsx | 95 + .../src/components/RelativeTimestamp.tsx | 24 + js/mobile/src/config/env.ts | 63 + js/mobile/src/features/auth/SignInScreen.tsx | 193 + .../compose/AudioRecordingOverlay.tsx | 125 + .../src/features/compose/ComposeDock.tsx | 298 + .../src/features/compose/ReviewSheet.tsx | 154 + .../src/features/compose/TextComposeModal.tsx | 152 + .../compose/VideoRecordingOverlay.tsx | 138 + js/mobile/src/features/networks/Drawer.tsx | 167 + .../features/networks/NetworkListScreen.tsx | 136 + .../src/features/settings/AccountScreen.tsx | 37 + .../src/features/settings/SettingsScreen.tsx | 27 + .../stream-view/DeletedParticleView.tsx | 52 + .../stream-view/FallbackParticleView.tsx | 89 + .../stream-view/MediaParticleView.tsx | 273 + .../stream-view/PlaybackPageIndicator.tsx | 101 + .../features/stream-view/ReactionSheet.tsx | 367 + .../features/stream-view/ReactionStack.tsx | 124 + .../stream-view/RenameStreamSheet.tsx | 89 + .../stream-view/StreamActionsSheet.tsx | 118 + .../stream-view/StreamMembersSheet.tsx | 273 + .../stream-view/StreamMetadataHeader.tsx | 64 + .../features/stream-view/StreamTopActions.tsx | 110 + .../src/features/stream-view/StreamView.tsx | 646 ++ .../features/stream-view/StreamViewScreen.tsx | 48 + .../features/stream-view/TextParticleView.tsx | 114 + .../stream-view/stream-presence-context.tsx | 203 + .../features/stream-view/stream-safe-area.tsx | 28 + .../stream-view/use-exit-countdown.ts | 48 + .../src/features/streams/NewStreamScreen.tsx | 206 + js/mobile/src/features/streams/StreamCard.tsx | 156 + .../src/features/streams/StreamListScreen.tsx | 140 + .../streams/VisibilityPickerSheet.tsx | 218 + js/mobile/src/firebase.ts | 30 + js/mobile/src/hooks/use-channel.ts | 85 + js/mobile/src/hooks/use-event.ts | 18 + js/mobile/src/hooks/use-networks.ts | 23 + js/mobile/src/hooks/use-particle.ts | 186 + js/mobile/src/hooks/use-stream-particles.ts | 95 + js/mobile/src/hooks/use-stream-playback.ts | 258 + js/mobile/src/hooks/use-suspend-playback.ts | 17 + js/mobile/src/lib/errors.ts | 90 + js/mobile/src/lib/firestore-particles.ts | 435 ++ js/mobile/src/lib/humans.ts | 43 + js/mobile/src/lib/particle-path.ts | 70 + js/mobile/src/lib/pusher-client.ts | 286 + js/mobile/src/lib/pusher-provider.tsx | 67 + js/mobile/src/lib/query-client.ts | 56 + js/mobile/src/lib/random-name.ts | 19 + js/mobile/src/lib/stream-visibility.ts | 29 + js/mobile/src/lib/time-utils.ts | 20 + js/mobile/src/lib/upload.ts | 201 + js/mobile/src/lib/utils.ts | 15 + js/mobile/src/navigation/RootNavigator.tsx | 55 + js/mobile/src/navigation/types.ts | 22 + js/mobile/src/stores/auth-store.ts | 137 + js/mobile/src/stores/playback-pause-store.ts | 33 + js/mobile/src/stores/session-store.ts | 46 + js/mobile/tailwind.config.js | 53 + js/mobile/tsconfig.json | 23 + js/mobile/yarn.lock | 5878 +++++++++++++++++ 110 files changed, 14682 insertions(+), 22 deletions(-) rename .github/workflows/{deploy-worker.yml => deploy-particleprocessor.yml} (93%) create mode 100644 js/mobile/.gitignore create mode 100644 js/mobile/app.config.ts create mode 100644 js/mobile/assets/flowy.icns create mode 100644 js/mobile/assets/flowy.ico create mode 100644 js/mobile/assets/flowy.iconset/icon_128x128.png create mode 100644 js/mobile/assets/flowy.iconset/icon_128x128@2x.png create mode 100644 js/mobile/assets/flowy.iconset/icon_16x16.png create mode 100644 js/mobile/assets/flowy.iconset/icon_16x16@2x.png create mode 100644 js/mobile/assets/flowy.iconset/icon_256x256.png create mode 100644 js/mobile/assets/flowy.iconset/icon_256x256@2x.png create mode 100644 js/mobile/assets/flowy.iconset/icon_32x32.png create mode 100644 js/mobile/assets/flowy.iconset/icon_32x32@2x.png create mode 100644 js/mobile/assets/flowy.iconset/icon_512x512.png create mode 100644 js/mobile/assets/icon.png create mode 100644 js/mobile/assets/icon_green.png create mode 100644 js/mobile/assets/icon_red.png create mode 100644 js/mobile/assets/images/FLOWY-4.svg create mode 100644 js/mobile/assets/sound.wav create mode 100644 js/mobile/assets/sounds/click.mp3 create mode 100644 js/mobile/babel.config.js create mode 100644 js/mobile/eas.json create mode 100644 js/mobile/global.css create mode 100644 js/mobile/index.ts create mode 100644 js/mobile/metro.config.js create mode 100644 js/mobile/nativewind-env.d.ts create mode 100644 js/mobile/package.json create mode 100644 js/mobile/src/App.tsx create mode 100644 js/mobile/src/api/client.ts create mode 100644 js/mobile/src/api/types.ts create mode 100644 js/mobile/src/components/Avatar.tsx create mode 100644 js/mobile/src/components/BottomSheet.tsx create mode 100644 js/mobile/src/components/ComposingIndicator.tsx create mode 100644 js/mobile/src/components/RelativeTimestamp.tsx create mode 100644 js/mobile/src/config/env.ts create mode 100644 js/mobile/src/features/auth/SignInScreen.tsx create mode 100644 js/mobile/src/features/compose/AudioRecordingOverlay.tsx create mode 100644 js/mobile/src/features/compose/ComposeDock.tsx create mode 100644 js/mobile/src/features/compose/ReviewSheet.tsx create mode 100644 js/mobile/src/features/compose/TextComposeModal.tsx create mode 100644 js/mobile/src/features/compose/VideoRecordingOverlay.tsx create mode 100644 js/mobile/src/features/networks/Drawer.tsx create mode 100644 js/mobile/src/features/networks/NetworkListScreen.tsx create mode 100644 js/mobile/src/features/settings/AccountScreen.tsx create mode 100644 js/mobile/src/features/settings/SettingsScreen.tsx create mode 100644 js/mobile/src/features/stream-view/DeletedParticleView.tsx create mode 100644 js/mobile/src/features/stream-view/FallbackParticleView.tsx create mode 100644 js/mobile/src/features/stream-view/MediaParticleView.tsx create mode 100644 js/mobile/src/features/stream-view/PlaybackPageIndicator.tsx create mode 100644 js/mobile/src/features/stream-view/ReactionSheet.tsx create mode 100644 js/mobile/src/features/stream-view/ReactionStack.tsx create mode 100644 js/mobile/src/features/stream-view/RenameStreamSheet.tsx create mode 100644 js/mobile/src/features/stream-view/StreamActionsSheet.tsx create mode 100644 js/mobile/src/features/stream-view/StreamMembersSheet.tsx create mode 100644 js/mobile/src/features/stream-view/StreamMetadataHeader.tsx create mode 100644 js/mobile/src/features/stream-view/StreamTopActions.tsx create mode 100644 js/mobile/src/features/stream-view/StreamView.tsx create mode 100644 js/mobile/src/features/stream-view/StreamViewScreen.tsx create mode 100644 js/mobile/src/features/stream-view/TextParticleView.tsx create mode 100644 js/mobile/src/features/stream-view/stream-presence-context.tsx create mode 100644 js/mobile/src/features/stream-view/stream-safe-area.tsx create mode 100644 js/mobile/src/features/stream-view/use-exit-countdown.ts create mode 100644 js/mobile/src/features/streams/NewStreamScreen.tsx create mode 100644 js/mobile/src/features/streams/StreamCard.tsx create mode 100644 js/mobile/src/features/streams/StreamListScreen.tsx create mode 100644 js/mobile/src/features/streams/VisibilityPickerSheet.tsx create mode 100644 js/mobile/src/firebase.ts create mode 100644 js/mobile/src/hooks/use-channel.ts create mode 100644 js/mobile/src/hooks/use-event.ts create mode 100644 js/mobile/src/hooks/use-networks.ts create mode 100644 js/mobile/src/hooks/use-particle.ts create mode 100644 js/mobile/src/hooks/use-stream-particles.ts create mode 100644 js/mobile/src/hooks/use-stream-playback.ts create mode 100644 js/mobile/src/hooks/use-suspend-playback.ts create mode 100644 js/mobile/src/lib/errors.ts create mode 100644 js/mobile/src/lib/firestore-particles.ts create mode 100644 js/mobile/src/lib/humans.ts create mode 100644 js/mobile/src/lib/particle-path.ts create mode 100644 js/mobile/src/lib/pusher-client.ts create mode 100644 js/mobile/src/lib/pusher-provider.tsx create mode 100644 js/mobile/src/lib/query-client.ts create mode 100644 js/mobile/src/lib/random-name.ts create mode 100644 js/mobile/src/lib/stream-visibility.ts create mode 100644 js/mobile/src/lib/time-utils.ts create mode 100644 js/mobile/src/lib/upload.ts create mode 100644 js/mobile/src/lib/utils.ts create mode 100644 js/mobile/src/navigation/RootNavigator.tsx create mode 100644 js/mobile/src/navigation/types.ts create mode 100644 js/mobile/src/stores/auth-store.ts create mode 100644 js/mobile/src/stores/playback-pause-store.ts create mode 100644 js/mobile/src/stores/session-store.ts create mode 100644 js/mobile/tailwind.config.js create mode 100644 js/mobile/tsconfig.json create mode 100644 js/mobile/yarn.lock diff --git a/.github/workflows/deploy-worker.yml b/.github/workflows/deploy-particleprocessor.yml similarity index 93% rename from .github/workflows/deploy-worker.yml rename to .github/workflows/deploy-particleprocessor.yml index a190cf3..55c047b 100644 --- a/.github/workflows/deploy-worker.yml +++ b/.github/workflows/deploy-particleprocessor.yml @@ -17,6 +17,6 @@ jobs: deploy: uses: ./.github/workflows/_deploy.yml with: - module: worker + module: particleprocessor environment: ${{ inputs.environment }} secrets: inherit diff --git a/go/Dockerfile.particleprocessorworker b/go/Dockerfile.particleprocessorworker index f68a2ac..385a93c 100644 --- a/go/Dockerfile.particleprocessorworker +++ b/go/Dockerfile.particleprocessorworker @@ -14,6 +14,8 @@ RUN ls FROM alpine:latest AS second-stage +RUN apk add --no-cache ffmpeg ca-certificates + WORKDIR /app COPY --from=first-stage /app/cmd/particleprocessorworker . RUN echo "copied over binary to production stage" diff --git a/go/cmd/particleprocessorworker/main.go b/go/cmd/particleprocessorworker/main.go index 6642d42..fe58a22 100644 --- a/go/cmd/particleprocessorworker/main.go +++ b/go/cmd/particleprocessorworker/main.go @@ -1,11 +1,13 @@ package main import ( + "bytes" "context" "fmt" "log" "log/slog" "os" + "os/exec" "strings" "time" @@ -107,6 +109,7 @@ func main() { updateParentLastChildCreatedAt(ctx, change.Doc) transcribeMediaParticle(ctx, depotSvc, speechSvc, change.Doc) + transcodeMediaParticle(ctx, depotSvc, change.Doc) recordFreemiumUsage(ctx, billingSvc, change.Doc) if err := processingRepo.MarkProcessed(ctx, particleID); err != nil { @@ -162,6 +165,152 @@ func transcribeMediaParticle(ctx context.Context, depotSvc depot.Service, speech slog.Info("transcribed media particle", "particleID", doc.Ref.ID) } +// transcodeMediaParticle produces an iOS-playable MP4/m4a derivative for media +// particles whose original mime type AVPlayer can't decode (notably the WebM +// the desktop recorder emits today). Skips when the source is already in an +// iOS-playable family or when a transcoded variant has already been written. +// +// ffmpeg reads directly from the GCS signed URL and writes to a local temp +// file — `+faststart` requires seekable output, so a stdout pipe wouldn't work. +// The temp file is then streamed to GCS via depotSvc.CreateFromReader (no +// presigned-PUT round-trip — the worker has direct SDK access). +func transcodeMediaParticle(ctx context.Context, depotSvc depot.Service, doc *firestore.DocumentSnapshot) { + var mediaParticle particle.FirestoreMediaParticle + if err := doc.DataTo(&mediaParticle); err != nil { + slog.Error("transcode: unable to marshal particle data", "error", err) + return + } + + particleType, err := particle.ParseParticleType(mediaParticle.Type) + if err != nil { + slog.Error("transcode: invalid particle type", "error", err) + return + } + if particleType != particle.TypeMedia { + slog.Info("transcode: particle is not of type media") + return + } + + // Already transcoded — re-delivery within the 5-min Firestore window. + if mediaParticle.Properties.TranscodedObjectId != "" { + return + } + + // Source is already iOS-playable; nothing to do. + if isIOSPlayableMime(mediaParticle.Properties.MimeType) { + slog.Info("transcode: skipping because already playable on ios") + return + } + + sourceURL, err := depotSvc.GetDownloadURL(ctx, mediaParticle.Properties.ObjectId) + if err != nil { + slog.Error("transcode: failed to get download URL", "error", err, "object_id", mediaParticle.Properties.ObjectId) + return + } + + isAudio := strings.HasPrefix(mediaParticle.Properties.MimeType, "audio/") + var outputExt, outputMime string + if isAudio { + outputExt = ".m4a" + outputMime = "audio/mp4" + } else { + outputExt = ".mp4" + outputMime = "video/mp4" + } + + tmp, err := os.CreateTemp("", "transcode-*"+outputExt) + if err != nil { + slog.Error("transcode: failed to create temp file", "error", err) + return + } + tmpPath := tmp.Name() + tmp.Close() + defer os.Remove(tmpPath) + + transcodeCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + + var args []string + if isAudio { + args = []string{ + "-y", "-i", sourceURL, + "-vn", + "-c:a", "aac", "-b:a", "128k", + "-movflags", "+faststart", + tmpPath, + } + } else { + // Cap encoder parallelism and lookahead to keep memory bounded — screen + // recordings come in at native display resolution (often 1440p–4K) and + // libx264's per-thread lookahead/reference buffers blow past the worker's + // memory limit otherwise. Output is also downscaled to 1080p max, which + // mobile playback won't notice; the original WebM stays in GCS untouched. + args = []string{ + "-y", "-i", sourceURL, + "-vf", "scale='min(1920,iw)':-2:flags=lanczos", + "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", + "-pix_fmt", "yuv420p", "-profile:v", "baseline", "-level", "3.1", + "-x264-params", "rc-lookahead=20:ref=2", + "-threads", "2", "-filter_threads", "2", + "-c:a", "aac", "-b:a", "128k", + "-movflags", "+faststart", + tmpPath, + } + } + + cmd := exec.CommandContext(transcodeCtx, "ffmpeg", args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + slog.Error("transcode: ffmpeg failed", "error", err, "stderr", stderr.String(), "particleID", doc.Ref.ID) + return + } + + networkID, err := networkIDFromParticlePath(doc.Ref.Path) + if err != nil { + slog.Error("transcode: failed to derive network id", "error", err, "path", doc.Ref.Path) + return + } + + f, err := os.Open(tmpPath) + if err != nil { + slog.Error("transcode: failed to open transcoded file", "error", err, "path", tmpPath) + return + } + defer f.Close() + + newObj, err := depotSvc.CreateFromReader(ctx, depot.CreateFromReaderInput{ + Prefix: networkID, + Name: "transcoded" + outputExt, + ContentType: outputMime, + }, f) + if err != nil { + slog.Error("transcode: failed to upload transcoded object", "error", err, "particleID", doc.Ref.ID) + return + } + + _, err = doc.Ref.Set(ctx, map[string]interface{}{ + "properties": map[string]interface{}{ + "transcoded_object_id": newObj.ID, + "transcoded_mime_type": outputMime, + }, + }, firestore.MergeAll) + if err != nil { + slog.Error("transcode: failed to update particle in firestore", "error", err, "particleID", doc.Ref.ID) + return + } + + slog.Info("transcoded media particle", "particleID", doc.Ref.ID, "transcoded_object_id", newObj.ID, "mime", outputMime) +} + +func isIOSPlayableMime(mime string) bool { + switch mime { + case "video/mp4", "video/quicktime", "audio/mp4", "audio/aac", "audio/x-m4a", "audio/mpeg": + return true + } + return false +} + func toFirestoreTranscript(result *speech.TranscriptResult) particle.FirestoreTranscript { words := make([]particle.FirestoreTranscriptWord, len(result.Words)) for i, w := range result.Words { diff --git a/go/go.mod b/go/go.mod index 7a0ec0f..2f54f7f 100644 --- a/go/go.mod +++ b/go/go.mod @@ -177,6 +177,7 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect @@ -184,3 +185,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.110.1 // indirect ) + +tool go.uber.org/mock/mockgen diff --git a/go/go.sum b/go/go.sum index 0a93052..7ef5333 100644 --- a/go/go.sum +++ b/go/go.sum @@ -477,6 +477,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/go/internal/billing/service.go b/go/internal/billing/service.go index f2bfa65..fabea1a 100644 --- a/go/internal/billing/service.go +++ b/go/internal/billing/service.go @@ -15,7 +15,7 @@ import ( stripesub "github.com/stripe/stripe-go/v85/subscription" ) -//go:generate mockgen -source ./service.go -destination ./mocks/service.go +//go:generate go tool mockgen -source ./service.go -destination ./mocks/service.go type Service interface { GetStatus(ctx context.Context, networkID string) (*Status, error) diff --git a/go/internal/depot/models.go b/go/internal/depot/models.go index 3f11d1e..8042a72 100644 --- a/go/internal/depot/models.go +++ b/go/internal/depot/models.go @@ -29,6 +29,15 @@ type PrepareUploadResult struct { UploadHeaders map[string]string } +// CreateFromReaderInput is for server-side direct uploads (no presigned URL). +// Used by background workers that already have the bytes on hand and don't +// need a client round-trip. +type CreateFromReaderInput struct { + Prefix string // Optional prefix for organizing objects (e.g., network_id) + Name string + ContentType string +} + // Config holds configuration for the depot service type Config struct { GoogleServiceAccountEmail string diff --git a/go/internal/depot/service.go b/go/internal/depot/service.go index a9c1290..4026552 100644 --- a/go/internal/depot/service.go +++ b/go/internal/depot/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "time" @@ -20,6 +21,7 @@ const ( type Service interface { PrepareUpload(ctx context.Context, input PrepareUploadInput) (*PrepareUploadResult, error) ConfirmUpload(ctx context.Context, objectID string) (*Object, error) + CreateFromReader(ctx context.Context, input CreateFromReaderInput, body io.Reader) (*Object, error) GetByID(ctx context.Context, objectID string) (*Object, error) GetDownloadURL(ctx context.Context, objectID string) (string, error) Delete(ctx context.Context, objectID string) error @@ -155,6 +157,56 @@ func (s *serviceImpl) ConfirmUpload(ctx context.Context, objectID string) (*Obje return s.repo.getByID(ctx, objectID) } +// CreateFromReader streams bytes directly to GCS using the storage client and +// records the depot_objects row in one shot. Unlike PrepareUpload, there is no +// signed URL or client round-trip — the caller already has the bytes. Intended +// for worker-side flows (e.g. transcoded media variants). +func (s *serviceImpl) CreateFromReader(ctx context.Context, input CreateFromReaderInput, body io.Reader) (*Object, error) { + if input.Name == "" { + return nil, errors.Join(ErrInvalidInput, errors.New("name is required")) + } + if input.ContentType == "" { + return nil, errors.Join(ErrInvalidInput, errors.New("content_type is required")) + } + + objectKey := fmt.Sprintf("%s/%s/%s", input.Prefix, uuid.New().String(), input.Name) + + w := s.storageClient.Bucket(s.bucketName).Object(objectKey).NewWriter(ctx) + w.ContentType = input.ContentType + if _, err := io.Copy(w, body); err != nil { + // Close to release resources, then surface the original copy error. + if cerr := w.Close(); cerr != nil { + slog.Warn("failed to close GCS writer after copy failure", "error", cerr, "object_key", objectKey) + } + slog.Error("failed to stream object to GCS", "error", err, "bucket", s.bucketName, "object_key", objectKey) + return nil, err + } + if err := w.Close(); err != nil { + slog.Error("failed to close GCS writer", "error", err, "bucket", s.bucketName, "object_key", objectKey) + return nil, err + } + + obj := &Object{ + Name: input.Name, + ContentType: input.ContentType, + ContentLength: w.Attrs().Size, + BucketName: s.bucketName, + ObjectKey: objectKey, + ContainsContent: true, + } + + created, err := s.repo.create(ctx, obj) + if err != nil { + // Best-effort: clean up the GCS object since we can't track it in the DB. + if delErr := s.storageClient.Bucket(s.bucketName).Object(objectKey).Delete(ctx); delErr != nil { + slog.Warn("failed to clean up GCS object after db create failure", "error", delErr, "object_key", objectKey) + } + return nil, err + } + + return created, nil +} + func (s *serviceImpl) GetByID(ctx context.Context, objectID string) (*Object, error) { obj, err := s.repo.getByID(ctx, objectID) if err != nil { diff --git a/go/internal/livestore/membershippublisher.go b/go/internal/livestore/membershippublisher.go index b922a01..a9d47f3 100644 --- a/go/internal/livestore/membershippublisher.go +++ b/go/internal/livestore/membershippublisher.go @@ -6,7 +6,7 @@ import ( "cloud.google.com/go/firestore" ) -//go:generate mockgen -source ./membershippublisher.go -destination ./mocks/membershippublisher.go +//go:generate go tool mockgen -source ./membershippublisher.go -destination ./mocks/membershippublisher.go // MembershipPublisher publishes network membership changes to the live store // (Firestore) that clients subscribe to. Postgres remains the source of truth; diff --git a/go/internal/particle/firestore_types.go b/go/internal/particle/firestore_types.go index fd93ff1..16085d5 100644 --- a/go/internal/particle/firestore_types.go +++ b/go/internal/particle/firestore_types.go @@ -35,11 +35,13 @@ type FirestoreTranscript struct { } type FirestoreMediaParticleProperties struct { - ObjectId string `firestore:"object_id"` - MimeType string `firestore:"mime_type"` - DurationMs int `firestore:"duration_ms"` - SizeBytes int `firestore:"size_bytes"` - Transcript *FirestoreTranscript `firestore:"transcript,omitempty"` + ObjectId string `firestore:"object_id"` + MimeType string `firestore:"mime_type"` + DurationMs int `firestore:"duration_ms"` + SizeBytes int `firestore:"size_bytes"` + Transcript *FirestoreTranscript `firestore:"transcript,omitempty"` + TranscodedObjectId string `firestore:"transcoded_object_id,omitempty"` + TranscodedMimeType string `firestore:"transcoded_mime_type,omitempty"` } type FirestoreStreamParticle struct { diff --git a/go/internal/particle/networkmembershipchecker.go b/go/internal/particle/networkmembershipchecker.go index 3d7bd7b..7886d1e 100644 --- a/go/internal/particle/networkmembershipchecker.go +++ b/go/internal/particle/networkmembershipchecker.go @@ -2,7 +2,7 @@ package particle import "context" -//go:generate mockgen -source ./networkmembershipchecker.go -destination ./mocks/networkmembershipchecker.go +//go:generate go tool mockgen -source ./networkmembershipchecker.go -destination ./mocks/networkmembershipchecker.go type NetworkMembershipChecker interface { // IsMember returns true if the humanId is a member of the network. diff --git a/go/internal/testhelper/mocks/aero/gen.go b/go/internal/testhelper/mocks/aero/gen.go index d4de8e5..0be8b88 100644 --- a/go/internal/testhelper/mocks/aero/gen.go +++ b/go/internal/testhelper/mocks/aero/gen.go @@ -3,4 +3,4 @@ // directive lives here rather than next to the source. package aero -//go:generate mockgen -destination ./mock_aero.go -package aero github.com/flowy-live/llink/genproto/aero PrimaryClient +//go:generate go tool mockgen -destination ./mock_aero.go -package aero github.com/flowy-live/llink/genproto/aero PrimaryClient diff --git a/go/k8s/dev/particleprocessorworker.yaml b/go/k8s/dev/particleprocessorworker.yaml index 6657255..7a69497 100644 --- a/go/k8s/dev/particleprocessorworker.yaml +++ b/go/k8s/dev/particleprocessorworker.yaml @@ -21,11 +21,11 @@ spec: image: "particleprocessorworker" resources: requests: - memory: "52Mi" - cpu: 50m + memory: "1Gi" + cpu: 500m limits: - memory: "52Mi" - cpu: 50m + memory: "1Gi" + cpu: 500m env: - name: "GCP_PROJECT" value: "flowy-dev-440017" diff --git a/go/k8s/prod/particleprocessorworker.yaml b/go/k8s/prod/particleprocessorworker.yaml index 7fca618..41d7082 100644 --- a/go/k8s/prod/particleprocessorworker.yaml +++ b/go/k8s/prod/particleprocessorworker.yaml @@ -18,11 +18,11 @@ spec: image: "particleprocessorworker" resources: requests: - memory: "52Mi" - cpu: 50m + memory: "1Gi" + cpu: 500m limits: - memory: "52Mi" - cpu: 50m + memory: "1Gi" + cpu: 500m env: - name: "GCP_PROJECT" value: "flowy-prod-440017" diff --git a/go/skaffold.yaml b/go/skaffold.yaml index 1669737..613f22c 100644 --- a/go/skaffold.yaml +++ b/go/skaffold.yaml @@ -59,7 +59,7 @@ profiles: apiVersion: skaffold/v4beta11 kind: Config metadata: - name: worker + name: particleprocessor build: local: {} tagPolicy: diff --git a/js/desktop/src/api/types.ts b/js/desktop/src/api/types.ts index 42e014a..1ab02d1 100644 --- a/js/desktop/src/api/types.ts +++ b/js/desktop/src/api/types.ts @@ -123,6 +123,11 @@ export const MediaPropertiesSchema = z.object({ size_bytes: z.number(), transcript: TranscriptSchema.optional(), source: z.enum(["camera", "screen"]).optional(), + // Set by the particle processor worker once an iOS-playable MP4/m4a variant + // has been produced from a non-iOS-playable original (e.g. WebM from desktop). + // When present, clients should prefer these over object_id/mime_type for playback. + transcoded_object_id: z.string().optional(), + transcoded_mime_type: z.string().optional(), }); export type MediaProperties = z.infer; diff --git a/js/desktop/src/features/particles/media-particle-view.tsx b/js/desktop/src/features/particles/media-particle-view.tsx index d08b145..2efad22 100644 --- a/js/desktop/src/features/particles/media-particle-view.tsx +++ b/js/desktop/src/features/particles/media-particle-view.tsx @@ -33,12 +33,18 @@ export const MediaParticleView = forwardRef(null); const audioRef = useRef(null); - const isAudio = particle.properties.mime_type?.startsWith("audio/"); + const isAudio = activeMime?.startsWith("audio/"); useImperativeHandle(ref, () => ({ seek(deltaSec: number) { @@ -153,7 +159,7 @@ export const MediaParticleView = forwardRef {transcript && ( diff --git a/js/mobile/.gitignore b/js/mobile/.gitignore new file mode 100644 index 0000000..1d3894a --- /dev/null +++ b/js/mobile/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +.expo/ +dist/ +ios/ +android/ +*.log +.DS_Store +.env +.env.local +expo-env.d.ts diff --git a/js/mobile/app.config.ts b/js/mobile/app.config.ts new file mode 100644 index 0000000..cb8009b --- /dev/null +++ b/js/mobile/app.config.ts @@ -0,0 +1,66 @@ +import type { ExpoConfig } from "expo/config"; + +const APP_ENV = (process.env.EXPO_PUBLIC_APP_ENV ?? "dev") as "dev" | "prod"; + +const config: ExpoConfig = { + name: "Flowy", + slug: "flowy", + version: "0.1.0", + orientation: "portrait", + icon: "./assets/icon.png", + scheme: "flowy", + userInterfaceStyle: "automatic", + newArchEnabled: true, + splash: { + image: "./assets/icon.png", + resizeMode: "contain", + backgroundColor: "#000000", + }, + ios: { + supportsTablet: true, + bundleIdentifier: "ai.flowylabs.llink", + infoPlist: { + ITSAppUsesNonExemptEncryption: false, + }, + }, + plugins: [ + [ + "expo-build-properties", + { + ios: { + deploymentTarget: "16.0", + }, + }, + ], + "expo-secure-store", + [ + "expo-camera", + { + cameraPermission: + "Flowy uses your camera to record video messages.", + microphonePermission: + "Flowy uses your microphone to record voice and video messages.", + recordAudioAndroid: true, + }, + ], + [ + "expo-audio", + { + microphonePermission: + "Flowy uses your microphone to record voice messages.", + }, + ], + ], + experiments: { + typedRoutes: false, + }, + extra: { + eas: { + projectId: "e902f7e5-e514-42bd-9591-75738e3494ed" + }, + + appEnv: APP_ENV, + }, +}; + +export default config; diff --git a/js/mobile/assets/flowy.icns b/js/mobile/assets/flowy.icns new file mode 100644 index 0000000000000000000000000000000000000000..310b5ef589d08012b6ee1f731f15522345432af1 GIT binary patch literal 59660 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 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.ico b/js/mobile/assets/flowy.ico new file mode 100644 index 0000000000000000000000000000000000000000..f097dba266b29e0280413fe218d01951f029e301 GIT binary patch literal 6792 zcmb7I2{hF2*M9~fSxc5|LnX^3l2UfYHWY@0?1Nzy9xg&U?=P{h#xG&-u=Cp69vqJ z01p2<|49MBTWC#O{O??Z5`d>vkO1`jk^g)WfCd@>qI7SmGtix(gO(XIHIQg%guV!v zX{n)KzV#z3XgKbsVeAP2W6PfxmL$r^1$~1<6RBh%-APDnbZ8c5eyAXNi=H}MFN0sD zDW-3>G*Vt;&xZK8U1-2lk&Skhmy+{z$F$PY<5w*^{esS}GVbJw)my$K^_V!G+yP2m zFpH|EF(G&dQ|qGMQ}kS83+Xp$41#SXDf}`x%C9V9LdRHCSb&y5bwJCXU+BPWOR&9~ zZuRK;F+2tUeyZ9cuI;(Cltr(A+iF3(k8L^iD$&GqGx23!YETSzS(L zZeNQJujc5hx;PmKe#_D*qBPU;q!-+}&8YoWjR}U7Bf(SU=EVtUfn2I;S`tl@M-6xW zh|4=8$71Js7i#{9t;zG!-)V7q_m`MI&c=OL?Z2`)B9Uaf2uX@2pIb_Q%Rb&BWv{4rlaO+m99kPCY$N zfB#%e^yA1AcXE-EPjzdFm<F`9m`j~Myv&Bv z0p=`yCY;f~$91WhxwaylF+AZ@yTlcswmB0+~n_{wxe zqotit?qh+_7t*+-QT7YC0{6!)XFPaX=QjuhtkW^D-$uE(&ZuBmC z!37d*W7nOj6did?&@7<~{;85W43@@*Kk0>Xp=~#B?@d2|q-qw|6=I$C@9cSEDn>)_ z{(gGG=opepAk41(c)NSnK<30Ii3A9na}-@&-e&XRoqN9*5l2i*0i-Ltz~(Sau>ye0&y*=irB;zC*U z5;(q!w>$_#)=1XGiv_`Nwsp0%m^yY}UBB5~GquBe4W`C1V;epzYbwNQ2eYXhlBDEU z8K82rAM<(dO7VL~ma>G=l2VZoaFMb_fGWs@1$lF;Zz8gEh3flEu#_dz=N|5#%VDZ= zzJobY&EI$eE@_jV3`1Ox(~m_=w_UAMk=?1%wkIS&gE?0;)^)` zO}qr-f-KXO&%`oJ51;Z%H?P$nb>}X=OI=3&q;Wot=#Z_%P!N)J>e_>QamaC;fk-4$ zz{H4|3PN-JVGfd_z<>_8xx2%J=lf4+2qqdC8%LRYqpSi0xzMPto*s;g3nOsz@Sqs> z-ZSJ#urfBRPnM(>aEOUb`_OO?PD|m|efAV7m=7iF=>W}ER#rHM?`BGw8*-f&u({0n zC^lC0_tD5msxd|0>(`|vCCoeiJ?qnZhaJknFIvcY#lzW^jFq}?1~1>mIgjZmd)Q~hD&TQ z3cR-|m6$gIGM!H$a)UO$tTc|RrBkmv`<#a7%6{C8*T_>k&L3Ax_ibvb_&z}^uXNZX z8I_B^h-R=STw`D@;jF=P#kM^g3h06V4vba9h?e0{xe)0r`c3XIz%4d|u>^Dh-pF~t zdqTf^rwsj>*JMz{*0Vd=Y*>!LjFvCp!_LkQU+zG1L4j~Qe)43WXv_Y+@@tcyPb&xg zmR-fvm>%*tCef-~PPJL|x*>V}dg}R{RM<%Fr|SxD2OW~$yb%p$QRB!KJ)~e7)AKXU zd^`<-|35GVO4d+j{)Z{19{G0A?B8XClR`y~F^0o8vPZH8Qry5Ov z^R5WbPqtKcv*{{b3sV|Z>nyxU-F;p<)|+AS9JG&Xi}VzWRjsIs`g7d+PUyZ;CFHq% z%~N7HdzxY-#~xK?v#h2mSo2E5LDl0<;8e}?ewu_rr_PhYpX$mbkN6aSeh~D%XV-YN z({0*Kf&BkI8y%Vv@63o}?~1>8Pw;~G0$)W1(kq9Y1mTA_+UM?*A;s7LdLh-3Jg3x*dvp78jk9Rc70(hhNy!1mW2x$hNtNS zCUCZ2nURyBYoS zLb;ZYgn?f($2$83os8$jgU+Q6o3Tvv^!p9?eGTEww-SQghM6L^JLPQpDB`uYpqF)Z zGEy=!fe&~TZGY^@wWTytk@k6L!t)FDDG8hDpaJ{)`zIo7OI>^F3UrIj)al$3=9khQTTdTR&1+|5}^3wQbz<89_zetd{2q-PlZuMU6nlb#?V7%ZOlW9#clSP&X#6_HiGK0f9z($n88rBU2|K|#;NLHn2XY z$YLno#x3o=WN|;U?E+K|Bq$%O)Ul^l0e5gU+JxWN!d*vh9z0^2~ zAJQ6l;B2!2l~R{^%s(%Wf}pPbxZ8si;ya zm%jpQ5(P-dqEym2ZNzji!*!NY@hA!{Nr0D@hJNrcMI=2C$e_$G0Mq(7B+DJk}#M z^YR4!GT!|ljZ`|amyIBbEE>C+@tdCD?2)hR+vxh2oj{O zI*&#?d-jZmP8&my=u~!fz2wS>0S1kyR98v%J{K5#P?Y``yaSj7J?y4Ok}L z%vb|v7D^nx@)LjyKr0de|1tIgEb3qOCLP-_GEqOIYzl4y_;VO~Hg@*2b*8EKECZ35 zA79ezMdM-29~kb?NlHo@85u!UO9EY3QY8>Gug3?;_egP_MU?4KM$>Y)e~3DDp(J?E z$IHj(W7%5`r$G74_W90a2?+_7ROdVqYDL}5eKacD2x0ByL?0U)TS6o)WJ^%bdvMNs zq_*-o%^06VbX?5CDM^8Nzh-h~fa5PUUa`zx}oLwQrbse%^S9Vvs>3Q)bdR$UfV_hBB-FGgLt9vs%Y87n{7`RFT z$u5w{^UUp0mW($*iN~z2wpKvm+LkPnv$M0ki_4E|Fui$7I9^pyAi;=we=F3GuGHvJ zre9&uU)Mp3RM6F*di;+fcxVQh;IAv~8Me<8S^$1l$&jwblo2{a{IUKEZgMzvc!z54 z)3D{*?Vm5>r)$MBsn1b2y?j;4=;Y}9Na<>NYvc)baRmj)3Vuy^7%AWAS)^97n3aQr z$m7=eJ$Y2_SK{gT{HiuKYDEU2BvgmZ3Cq&0TjFs|W zgn!ivV=1M~@0Cr(OCN*hfI&eBbHXY}So8T0|BKuIf+GiDz1qgH-C&V4)=@d6w9RH) zM5EYuF=VW2t>u(X*2|YKbK*^6Xz3y;1TS1j&=!2NlX7jCGRvL~i(qSpf^Og7;4zGL zYaI@vqL;7l+{R)*#BSP62NXS^Vp)JxfAP@k#(^%`FEE4Li+!0V zk3jLixb@ONL~h_}PfyR36m&U`o3nIxFI{4q#?uB(wSWAmoFDIML#Zen2^Gq((8AxU z-sgoQ1fY6Ax75LOI(l zumwLGlAfO4yuUANUHYvsq@5h>mqC*|5KK-*)1_)tV=x#<<f&d+*YsxOBxVji#d+bPSbK>nMlVsaU=>Sp=K7r*F~=~^hE|e+OF|>e@`eqmQ++c zT?;-s>jRMFRZua+cM*a3u6zoBtV;P0&01dAqGiLkh9YcW-oszpk+?=WIpLY*Wjf4r z!VExdp7pjA(dX366Xgd-ejy=;K|d*`M=Kq*G8TAj0dsH&*lxgHN&JL}6a&v*!xV;< zc7OZkSXLg@nu;COKPfiu0}#T_HmrJ{QS?ggo8s_1pM;8yxC)+pvOzl@SU~#pP~(i9^7}*y*7oq}U77anr-O=~kF=ln_v7YtGnt;uv z(!%Ygk9j$iv!*Da8>c8P@MxJ_|mltAO zN!D#8UP2%c^-FspPm+%bJ0Pht7S6M?v6&y!#ajbQ7nfg7P!*8GM-!c6UAi4!Q&UrZ zo2Xn^&s2YcU2H#bPFj{gJGQBv!3N>;81 zl|$>FY<|l?Ro5KQ9-wTpDIZyeGTY*V2M>zFrtWCWFoH{opX#qjNo|%p#2+dh2Y{qs zFfh-+b@lC=H*dz;Pe%y9C1A64w`X#-k`L)_%dW9QO<_Q8i{-ipCHoE0Qlk~k2OyHL zt(E)rE=1NnNpJ$HvX$rnCENnRrc(@M8XW}NvMsHyQFNoJ7+?DH=Rtk$&kG!S_QKBq zV9DLCncO*_xIA1u)v^G1!2B-MRvUB?P!;zuHIlvtfOGC+I8rps%g1jrg2Vmr!+3US5u^udd#P6_Q?A zT!ebx+mXsv{Fy!RZ|}YX(o<;{P@vcMWxxv%xpsc znXObgS5{W#zRB_q6j(oRZ*Nb5YTlOX*o#nu-5#_WhVmdSHKl{}BT`7Waa9G)}e%->c_U)WKqp-le6bHDJbkXc`@Gg;Gm_8OdD>>sUE? z`NK=VE>uXZ8o06%@U+SubC|zIOz{ppi4a9cv~$*agp0H`=c8s0o7T%dL6tWIH(L zz8sp7mVte+h)8=s&QjqrS?}-b=(v1md3iZg59O?n3bj&X(%tAau@Fw~Tt$e99j@$d zF5iP8cx3%byf%LJ=^`BY+WLPwT&GRr;^GP%9UG&8Oc8RHH=fD(*=7X#GqsxxJqI0P zOXg_MB(Pys=SG}lTF=7KAAG*1i~LBB8_!mGh=SSz#|JqVN-Wf4iX)UDG(st5)z!&q z9~B&Y;4)UVmxO*HP!@82P56f44P}hBnxovR|77)?^gSq)r+VFxGZ$igQeA1_dmPr7 z`Vdx9ThwIE7_sdEcQShCVy5otx&F*gwxPA`typK7NfMgt&mC=Pl|C{_EA~yzC~2|t ziD=`?$~K2$!*Sx#`}NgHV&lcBM`cT7|Kj0;jb(+7z@v@8ot&zp?%|rTtxLZC=Lz0Q zjxV-?v7g8XkV$1Tdlv4MjW~Vvp4jmCAk(Q}PUuE<^wT literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_128x128.png b/js/mobile/assets/flowy.iconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..48bed99eddf216da1e7c1d4f90292142e3cfa3b7 GIT binary patch literal 3133 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 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_128x128@2x.png b/js/mobile/assets/flowy.iconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4af368921375af6d2ea7f7bf2dbd84189c30cf0f GIT binary patch literal 6796 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 D8MW{7 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_16x16@2x.png b/js/mobile/assets/flowy.iconset/icon_16x16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67ed0d354c0f56ecc1e5bb76bf82117a0d8b45d5 GIT binary patch literal 1001 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgQIQ(qnda-upao=eFt9L6 zF@Q{91Y$czX*k=BQGg$2wA zHb~oi&afjuinG8YvY3H^TNs2H8D`CqU|?Xn?djqe62bX4?DRSzM}gcp!6r^e4sK3P zZm~5caRrmIeeV1cuWLN!Us(8`b@~nkHLjh9x@22s6dZ2aZllEix@6hLT{9)sI(F@x z>$6Ng^!c1s>)uH!zPnw2&f!4s{tzwKjJK%@0u{UNzn9;2v-0fZ_j9i8pIE>4V#c46 zO{F3Mdee1{{yCgCZM69LbDnb1jk;yeYxl<*&5?&QXBw6OW`uZ$s z&H4743Nn1w%S4WJ{JV2vYR6=z4cq-p8@k-ftmgI|vU>eilc8F=|9I+D<*0iB87KIR zKB}J$W@vD1Xy2EXWFmF4csjXT|*6H*0&HwUcihj$ZOQ-!!4Q`EfSWi~OZ- zY`pe?#RB%V2~4xMZ8`SVl<{g-sjJrgNe_QaQp)*c_;BCrvfcZ+Tc*g=?s)(1-M(}q z$rUwDef<{9 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_256x256.png b/js/mobile/assets/flowy.iconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..4af368921375af6d2ea7f7bf2dbd84189c30cf0f GIT binary patch literal 6796 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 X_ZI7FzYG3VCqVtS&aM0#HlhCmnu;y| literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_32x32.png b/js/mobile/assets/flowy.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..67ed0d354c0f56ecc1e5bb76bf82117a0d8b45d5 GIT binary patch literal 1001 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgQIQ(qnda-upao=eFt9L6 zF@Q{91Y$czX*k=BQGg$2wA zHb~oi&afjuinG8YvY3H^TNs2H8D`CqU|?Xn?djqe62bX4?DRSzM}gcp!6r^e4sK3P zZm~5caRrmIeeV1cuWLN!Us(8`b@~nkHLjh9x@22s6dZ2aZllEix@6hLT{9)sI(F@x z>$6Ng^!c1s>)uH!zPnw2&f!4s{tzwKjJK%@0u{UNzn9;2v-0fZ_j9i8pIE>4V#c46 zO{F3Mdee1{{yCgCZM69LbDnb1jk;yeYxl<*&5?&QXBw6OW`uZ$s z&H4743Nn1w%S4WJ{JV2vYR6=z4cq-p8@k-ftmgI|vU>eilc8F=|9I+D<*0iB87KIR zKB}J$W@vD1Xy2EXWFmF4csjXT|*6H*0&HwUcihj$ZOQ-!!4Q`EfSWi~OZ- zY`pe?#RB%V2~4xMZ8`SVl<{g-sjJrgNe_QaQp)*c_;BCrvfcZ+Tc*g=?s)(1-M(}q z$rUwDef<{9 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/flowy.iconset/icon_32x32@2x.png b/js/mobile/assets/flowy.iconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d05c05314fa95043880fd59c9d29c164b6036e95 GIT binary patch literal 1693 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 X_ZI7FzYG3VCqVtS&aM0#HlhCmnu;y| literal 0 HcmV?d00001 diff --git a/js/mobile/assets/icon.png b/js/mobile/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e96a5a9cbd946f81304ca0997e2a622957934a GIT binary patch literal 35911 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 X-jauE7Y}|z|MT?8vnTS7Ip6#rTL-~1 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/icon_green.png b/js/mobile/assets/icon_green.png new file mode 100644 index 0000000000000000000000000000000000000000..009ff0ca8bd1b3ad1468454b8816a85886bb1964 GIT binary patch literal 36555 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%0DB)kgqGKbloDW#Bf4Z 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 C5k@lr literal 0 HcmV?d00001 diff --git a/js/mobile/assets/icon_red.png b/js/mobile/assets/icon_red.png new file mode 100644 index 0000000000000000000000000000000000000000..da4350598b3cf5e66aff09329142b41d5f45ac14 GIT binary patch literal 36464 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

HD*-zAcBwq1nO*4=IB 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 + + diff --git a/js/mobile/assets/sound.wav b/js/mobile/assets/sound.wav new file mode 100644 index 0000000000000000000000000000000000000000..68abf034ec52d907490ad5b8973c8d8bde1bcbc3 GIT binary patch literal 589902 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 zf65-*cP#>oz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b zBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ zECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4N zz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV z2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&y zi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;R zum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT7 z0*k;Rum~&yi@+kV2rL4Nz#^~+ECP$bBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b kBCrT70*k;Rum~&yi@+kV2rL4Nz#^~+ECP$b-&_R#U+%{qRsaA1 literal 0 HcmV?d00001 diff --git a/js/mobile/assets/sounds/click.mp3 b/js/mobile/assets/sounds/click.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4fd472cafc324de51bf4302da2ca9280af9ab820 GIT binary patch literal 33024 zcmbr_^;a9u+c4nZ!Gk-+2_CG)8??B)6I_d1fd+TC;O_3;;_gL{yfyf_^)!fGP9q7GVC1#B94N3R{aW34NOb%mricq+t!ZWwANi||l>mllKWNBOy6^a~RY_dHuf?nr zpgK4q#%UT!h&W;MCm|5?a&XcCR;OWPbP=6#;c!t_SAcu?@-;|Fg){^wm^)Y*;oHeH zl&cm$LF7rX!c*>G)^%4}6;5>Y&iIqd$?J2KnR!VRRBbD(fYEF_x-7OrW3l2rR^33G zvDFE31evx=Hc%wz4kQ&Y5KnsgJT-b$wyarJ_3`EZ%9DsrsTZ;)30(h<5`7Q_X?)s~ zjR2rSaLw>lS95Zmak^2B<4xfDaPYDA4{SLphKQ_QOpEJ)%Vcu11(u(GN&}o{gcvRG z)Mcb8B#iN*EeFUMy)d1q8~2YtI*4~|&YGZ$5bv}{TIzj#^~StZ*+?hitRuS8$4|xK zclkM|_XkIH@V)1XxX+`a{hy%czX1WVepR>r<7=0f2*Z}gm(MT1zj=RTW>XQ16#vUK zbRsmZ&o9yVJA$BGh8jku0qu55 z!R*P5B(xnfFz(F?0TWG77+?W;le0Ygvsp8WpB%oYI93J9y~8ZVQNeR_ipgduAz^Rh z>fT-%Q&{xOW{DZEvytO8ReE{(Tc5I`plX$b@xPt{kS&Y5^~=v=>Q!y%?ATDbZ>7s5 z9V=m$5skzorn**!{yergXLT}!17?0D%Lh#!01QgB@i>)HzHuypKX@k)3PF}VqLiu{=WEeMH|E|r)wQs`ffEu{Azd(MAvOD%v9dbzv+7V;1=1kB|Sk;f^Sqal~4@a9SC>=-`th)tz5iU8$R)rg>QgEZV1- z|Mxr^+wy8EBN!p-cBee*g>Ih7EF{D_H)ZBJad$~XqL6n3{bn*I;~-U);xqkHe@+rz z`bo{M^=>O3vi(c>V(vLwM9IOWlulfflTUU)&fEDf)z$QKFrj{%idh<2;e*KPk=Q(` zrS~5QF9^T3)u0$F+y7aTjHU8zOSAU6LeP9kQCSC?6zy~?W#kN?nzFo)ct4m@aQs6wwh}WiPRZ1+RpK~ zAF4YVPwG<8Dzd-Sm-P2y4TT#kDR_|?{0=tY&k=Vvfiw^QD&-m*Y`Wb05_G?P{=V*0 z!;Q;=!`n6Hm(VO^N^bP1*V0m`U|e018Sz##2-R7-`-l;Ve4_;W00+*JUVGl~rR#tG zN#STIO6v8_JAkfe`>R=V0GZbZm99uQ@{b0GkN@Jo8o)M#-_DmMbX!EKpR9#gjeMa3 zb2X4dgCMQa%~kkptex-~Nx!w?9d)<+)>svMv?7#H^yG(SQ=i^QV&*KLRLpqkMO;!- z&6xKWmC>O&Cx=GHHci98-%1oQw6t@+H_e~&5**I?%(x#OuA0%3xlQEJ;GAw@VO!$X zlDgsrFRODe2dY3SG&&W%4|U85lhtZdAAZ_$uBck}LVh&$1X`A!PEV>ChFwP7Gc1YUd9P%$_nxL>$OslN&#w0Cs%SR86(EMfs{bre0VZ zyf&wwrifVZ_ubc-DaW`jQesYD@W~+~kw(^0FNtSTHcY;?(+x-Aaj8kC?r(m?+xY@& z;S79|p53b=CLqI*YI=C7*!=U2Hm65BYlcI)D1l5EJEZVj; z9Gnh|cy6_COT^*|{=L?ZX{abWNT7@9sDv0RH~F#-=e82G>f2Xax3S@We5r`O56sL9 zH9OCjnT_tS**m9=6=tJ{Oi1AC>dyK^vaxY<*v6EN#Zh#$nM)xo~s?sUT7i{ZD-5>&iLWBXdrjI-3wbS z?aDd8@@Hd%zKEH~9Lk?5GL$0d;y|KFV1VO)5$NTm2+wv8a_*oGz7*tm_3!wv8znnn zimQ%;DrS?by@((~9i|5rnV&>&H}7xuJT=kmS{;nxUEP=Y&p#m?jm}v6yTuNmn1fwm zx)-3$f?@sN_$PwL>5&%%*hyhHde~TS8{l*f^ExAZI;=7^CONgy$C-Rh!kH$mk}@7^$)$cA2eTtt#vm99Zi(bdtS`WUtXeX_X*DV2RT z$TFp6Y;&Y6aaiPvG|U5bR$g_Aj-yBrBW&#bB|S;q4aEf4PlQ(8c06}fI*Qt9hF&>o zq*W~jZ#I2&syZK8aR6sRSSU$jFArvag=ZAJn2fDJ&`gbr|C;!({5ALEQ>XQkeBGnd zJfz>YiRKOmg7Ib$VA|ZIqDg76~?dBl>a=;vJQhomGqLY<}@x`V`!iEf+RW z6mZ)VJ-pn;p6$4tlI6e1_KWf`TYkW*a3;eLjY$;9nE}jGG5n zhR7Nj2)4>2#g-T1BUvudPr53S3p+p+Q5P%iGW20hEIKoA9S}t39e6gX4c-a)DT|z) z@g98#U)@@k$yC0?jRf1ujAAgUOOffc&QvM`UE4cWD3U2efoZ&EoHtOcQt05nF6wIdrips3nWe0mx{tm6ExF9U!T(_r7xe1#T} zu%uNSLANhvP#nW9tDyNZjbQ-d7g8Y_Q&mqi24qS~6xhwi4CLG8 za)=et8|vfD4&*->Fd$EIr66mP@1FG$d%axGFrw?s^Sm+w9Vn;rWziZIym)ZM@xK*_ zLM>}-_o%m|GzJOOEF%*T@I_;3A%{TuSSzOSc13G?38*ocM+|YX&)hj*JD1}PH~0Ts zIIX@$M|l4XirLh@6k$q`Fc78VK47NxZ~qaayxmGruBYOdXaH-CRW@>r8!2fPK=!4*$bJZG1gK1&G?Nu##=b4<;HZlk2?(uOqz=E zo48Gng~Day*2$T(ZsFW$@yfI>77yje!$VtnQR6@>>s*8L=$1Y&f1OX;GRMbUsq*Y| zL&;bL=W3a@PeBr1_i?MMZH-TF+HOfZV>Z5!%85$Ei@p0UP|rp9X0{{8QYVm7u!+;e zj3Ts?v$6ewn;DaYOCJm%w5xRZ9Fq&dgimc30T%Y^e0v-Xd~kpko~fq8%Whq3NkZ51 zFg1pvScq;1zATednFTD9g(b&N^C{B=p{~9-&pmJ5YLv*T^A}l5;E_e6MS0+*MY~#s zdroS7ScaqfeN7Cj=Vj&vd-AM%8|_b*r5?uo+^}U4QzT=TU|uq+#7+2QtFO(kk@-vmRhm?##iCWgdk8v87vmhu#CgpIwO7v zOB?4u|5k7`Y|~js1yiZQnrb!cXg_T3B*Y8+i3>yp0Rh56% z$It?Ne_LA7rD?dO(QAQXYH+KEi>p|~W!&YJBM77Q$7I-5@Mz^Wv1b2li_uqdM_fBA zp7u)BdT@L(=zZblwa_K&`5to~)nAB}+aDt2L+@3yTWpEtGQ)!@$kK38_lsQ3zO=N9 zqM1?j&#mYUs;gu# za(pnDb&K30vt9+)df&b$81fulMFE8kDVL5|$d-_T-KKt;-E0)SPG8X>X`@IMOE&QG zxn?JnV`?Sy-1O zFrKk`GDF7lx`Sb)iIGtOy^mYoeQ67uLZ#6Sy}DIeh>WPp1yxMg1gSqUfNN@!>lEA6 zE-A*DMmP-mqbO949;?if7&%UgRZvK5B={B+pe(@vp`$Jg!7By_rvT&0_2uY-^>2aX z9dc;>0&oF}Fb3-ZG-%GM&9u42Ekk9}dc3(y5f#C)H5{f6mkR(%h=fvlA2m_H(G72T zC`3`i3eVPqP?5A&#CT+VkyA#IT+diz7s7oFF)7LwBGe6##OzN!t??C4xkNH$TaOhx zCc{Y&GyKIx$iPmqN+q+AugMJ*u=tQ(7uYpD1jlVCDJ*pJT98>j8>Ej=WNEW%-75&o zLyBUw*0mKdUAJ+<6f9YG%x&cG8zUeCLiAa+Uo0&ggMs@3rB|Tiiz`d0fjZcLDi>y< zg4;IOXucg|SBrV{n(*&p`1Ii`gU?g`O$A&niyWdHA1n?24P;pU^PdL5^!Jhp&Gx_L znjKI2pk_)Y=*Y|s*-*TxvMQVA71sLV?Dp^*sH{Fse1fBkLkm3^Na6wvyf$}5J3`)( zG3P{M2V((l0at{0C`8KfSZU?}@F1BWI6|W}tP(2)gOt~PnR5tE%x$Yq{aP{-(*!9j z;w@iTeAd$337UGLa45u67Lc2a$1A;77)Rib$bzF6@I8!It*8fM4Iz5zf|Yq9EAv=K zCOC(eFSE%l2yc~SpHDHxlkP(&L@FGdYD9nPNU4&b#ynPbM6y#_O1N3FeiyL}dXuof z)hpaFBBZTZW5dFcmU_=eL0NUqpUJ##E?`KFw_}V{OLDM{L5+x{dbB!)cOs5&VkTH4 z>M|i~1dGF4_7pX?*wXG={5IY{yR1I)wMDS(ezr_bZi2e^Iq69`#yW`CwGrgqR;}gg zsI+I@@_A+R^;zsw4wJHiMMP@>xK+v=B5!<`hYQUcUFkS+QOK2v5hYO6OblccI95E$ zlFE>UI)}hV3h7{smNjmwsH&L85+Xy#ng=5+3S--pMS0sZ+_mtO=on8E!j;FD<~MB?KppT_xmTC1q0xl zh+VZ`DP06L0HH31h8qGmr^hzaW-KHqB$Sj)7d`v3of6W*;+mI8#Nl_m-TeTaSMT|u z|VC6Pk$We zvZH-y;yTskntuR)5Zw!I``pC$_|=h2B(zk%VPrZ&=Eal+Ze47X?X zwY#aFh>!)&rx`jq*BTGPXBD^47YL%GOV#JYbd>KPK&eVn)P5EpB^3y{kh)skVrP)P zu@O2C1tRg&LJP6szvjvYpNrB&o5qLO{_{@`Kr6JI&~hR*Z=G1D@roWMRq3423>a=;lYLbzpe6f}5#u1>D%_?O*KC2*OQ z;UnYyWU*;b7V8r?k>Z7>cMgwRjaCBA(V?AoSgN5 z%ueoM!4Rp&gN(K+-%FphtLKFUx(t=KC=F~@_3GSi=k;icY9 zNlViszVUjflnERTaF>g!t!k&BIB9hC=L1B>fiZoGF@e(4)Z|(6K}Y@AS$eIN9S;L+i|MluM zm4nawCI8jGqQ~XsD;0ajZkdsN+n1f<*&^rrV%(rRbAZCIWZI`l)Bwa<9L!aN4%AO) zi3LD|9#JBZlcR%ycsK&`i?%{3A=g~25rV?>A9JWFU%DN}UzEt-FepznPB}?}EYAsY zR`hIWdU$51r`1t`5LC`$E6mRA(v|RLzR9~3r@-zs6~gfe#=<*77GJh99Q~v*1-h{` z<#zH3s?RRjy`Cd}o-D*J>dVxVhh=(c0Kxz?^ip{;&KRjru0DLKl^J?s$@u!SU zoJ1?tre+5!;gJ64pAwGxps94OEZ|#+S*7B^iA(gX#8un4um@)=^BPX5rKhk@Hzv}` zuzUiWA17wP;~O}*x@~agR~IZoBQ#Yd*hQWTrWE#m!qlz$>?OWec0k)Cr;46`XB1C= zrH>|o^e~0SB6FN;-maU7f$Q%J6@G5$g%Nc4v~#X*2q|Y{rJ~doyY3~?ytMLD$1^CU zlpry3;<6?M#K1U3grNFvV5|onUQNo)xs#YQ?}`xjKU!7==Ah_RsltW zVZL(=bs24HWVZ#1cq)sM0@z`v!w)9956Q!(A*$^su9Y$~mx-*}bxwtn^Zp+{obx*K zIqKajzjd_XT@vMe8QNZTxDWX*ZU5EerRUn~=E`056?HpTarY87#<GoV>H2BqXopMLXTyxCU#5KJhbmSDO4G_wr}VfC%cL(lY8}+Zd+g0k6SS`5$>105g~2?>6%#s#?|_5{R4h2)P+e$ZNwrb&OFCdo65 zMEW_-pis!_0>T1FbMf$m-fYv+@aKp-Pn3ajd$OH>SoJ0R29 zXmZ^T7p#mW9D!lntk^23%{MKf)5^TGM6DlJ`%L}D>U$0jl}=PC_(hUGmvT$XDX$y$ z9b(&O>S5^|S4<%dEGAgLi$U*BMJs~#&k=Hx%A`clTT%u_rF+p=LV=nqO}dsIXq%PhbYV3&@H^rO(%F&)L_ID@cy&jH=}f;NOs-DFpWf1 zR*!IymT)vZ5_i~42rO`^icMHku;Op>7%6&mh`Dr|SKEP}CiNgr%&AK6`9`D7=#*vV zB>@XmKF?P`1|sc&1kjcrC=3O_rI7mJl;K)nn7@egN(Fyp5a;*K_xHxIFRy@Go$6s$ zL$C;fXjJ-;qjGq^0TFL`wt3RTBLy@(#6ALZ!zD26zBS`o{~PSmU@)tdFDE0xS<9)T zp{r1N`r(sEh!ze-p*yNAw1hB}NQ0^^c7*MKNPyJ{6ni)uXm?_RDJ>LjcjxB)x-Y#> z5Rb+(p3Qj%WNw?i2xZ)7ii4_S@*d!r4<|u)K}->9cd<6H63HVjJe&Os&?>^UFQg~6 zwPmE7_(|!k0@k?xYm}7Bt3VUM1I9zqsf3e3n%5#aZf8Fe^n;RDYIuixn9`$*%ukO8 z+K5mqr|Ckf5unO@$#6MJ0604A^*E`jAg+DvDqgxnoUgiDx6t$+8x96Ju2d*G>M)9P z;DnuKlqSSJaz+U-+XpqlYjA_ z80jtZW#i&t2%EKFJEc=^mwrrDTas+1?s;y&CG+S}-H~LIxZ-WjuZ9kiuR$4!7u__i zQ{Rj}er?aXzyI@a7zI0F{pzrw#Xi^7)k13eu?XUvGQ%jJ&agZ1^5|yH$D~Pam1%Zg zS|jH!CDUJoSu{`#mnsC~V`DH*2~i_^Wjh``N+$BnJG#V*sAi#WRfP% zwC*t^cgv2V)MfS>obX)%vnHw&K?--xQrty>q#6#F^ZbVEd_P|yqb8YurRtie&aH^i z@J3%bW@;4^zV#zYZvMrVQ6GbjsPia)6U7qk9cfDLGqiGfJ;_brzhDms>ea2}c0g5b9 zm^i>&N)pfljczjmE=wz18w89UPEytiMqexLb#?4Y=0rlEdch(^Ol&;H@Bpueai z*6WRUR-Z)(7P&AoBn-YE)o5W4ChVldRcssx=S*oQ?cbswnoIS z1K52WC0#%K9abb#k_9)!rdS>oKA(VL!l=i&aYjz!@s8}Vj-FV|vt_<3ZJj{y^~j)e zm1XL*XVynf)( zI!Yx!{Yee1wWFx)=QU4)1O7MvEeD`(Xv&}4Q_mLytGGNc@rZWcvDQHria#3?{Z?95*3v z)Kz(ZGX$<4vZe4Jes2gN1>1Ek19!e{a>ZZ$o_GhYjpm|~YBR4&?`Ge4A6{Zc6jE-K zc0i3~eB*TV-cgIhD5iIN%H)02jZ6$_2!RE_xAm5MqvqK#{|u@jNaf;u7lltU3*!^% zC6=WkI@wv_H z^)vdoQgb}`K@N-%mhU`JkG-;TT=8e9Y@hVBBcF!NjPbvBwo76>id<#zv2~wK)$^^ zH+=su{(k|Wq5bo(%zssgC5-FLSV>wmM||11fb~TfRy9h=cDUZavD`7KMSheOyn6bT z^l`2Ds`-(P1J*OM@B8(G4c@Yfc|)`@~?(RSSCZB(X|*2$V*v6w#>Sm+AKg!NIC-GuV-HOd+qx zr3zIS9TrZMWgf%kR8&?&Xl3eGZVa7KzKb`NefK4kApLWi1o{ZA`aa7;Vb{%sqLsVq zsJ@~-l3b}Qo7@-M-eSz|3jYSO!_1u8Ul%tUT%3~kKIQf*X$Mh%f3WL%Z!jNe%<1sQ z*Xe)LFMeLp@I9*L#AaNw?@2C7aR&d+%;+*q;Zbq`qL=_I!~Ld`B53dtpb+!MN;tL5 zI|u@`8VC!eE2li?z`>f*YX(7ocsQJYe*XtZ0T}3CMW;%>b^Z+SI`6PmwjBUb2)|1ptzkPZmFjVTN#4{x@r65hk& z2Cs%M!d4^FYyy3k)*oraqTb+D_^nx*5nGO_2CndD9~xd)7>|r>+RHKbwElc9+(a2{ z$l7Z6UNWs?6gju8*Yk#9ZnO4?WINi#Hkp=Wi2g|$3*Qx zOnEf4kOGm?v>9g8wAK$))+xnY=Fje06Fzjm%7L0dbex7tO2B{qe*nCW8AjFLv2!aHwbP*s8i~IT#l3*bz zlcGb^`}_Od=Gh?By+9ebtfQ2>Y+sE^6*T~AtadVWmV_OL$W5FA9IzG3ct=c95~Smh zdv^AfNOUc%OwY^*&C8TvBnjjjl#Ur7f03hZ&BYa>oi*6&z1dR~u6{AP$aMK|9JeCT z^1KzULvBe%^jX5U{hRAtLk_R<_rHI9q2v7L6Y%yTYX zA;X%pSv*5G^5AeN>ZR1Zn=gxQMk1SZOG)SJ2xatqVfEYAKj;k&BI$9`*s;qQ%2-ZE z16J2wl!@;z#mG%_Ti&eL?7JV-M6sHE4hR^(N7rMwWn4#dzI@L0=<6|AqBS+teUu+; z|ElILvvI|*@?Hu%L((g8AElDnJBF(DX(PT=$6zB8O};`B4g|<_&I1O?T41SEkmNgr z1Wbc1S`rCBuWkSVbigE~3NbCRCX*d&RX+JLU)$a~x41@@&5Lv9sSUN@-yt(0R;x+6 zBP~=IWj)sHI*dCk6bLPEB;>_~@wPD9o~wDNu&Sx(Ug4MPD6D)i|BIVyzTw0SZ+Xln?;uT(P8b&MPT2Q#6xpzAosc7*Vc~2 z`?eRwjqC5+6`Py88VA3>jx8O0vftr??+kuHa90QH?+6uxz7ZfR zp@}CWzbDgzL&ITkZu>~-=dbo=pZ@S9r_F8gP^!Pb^yB$-L-ZwfEbyb@MDOI)ZMyG8 zq9m&V_h#p(zt7ndII><-bRLA_9GQ=vhRbeGXXe!Ti!K6CzU0o|;UK8MU|}d`Oq?75 z52qS2jwFF2fPn^ZMuTGSEq-XS=|hpV=uXrQTrq)U<()|h-OaAz z8l#1qjRa1>F5S;G<=zmH;Ym^R^_E~^{WcRaI( zSMfoAf!_mkQBMB-{!0{Fl!D!R}{KpBXch1^XyG2y$VlEM0GA&6Rb;FoPDG_67pJi(kSbl!H z+a9;!P<-!yYs2kzs#VX}Crl2>eLM5<*P;tzHVifUncKX|t)lXE zOOW%^B8^QCy5PD?B^S%P&*%^#!v$m>@)r53T#Klc-kDH#T}jRpS~#DoOzYvhagS+{ zGZQKv&GzHc=H(z=@@2P~#%SmmcV&~ecoxOz0n;3V(S>1h_|4YnTv;i1Q4a5)84V9? zow`dce=7+DDimJEC=)`G{9O*6_+QqZ91pEXHyWvu+Po38gN`mEXIkLswPJ^b0;dNw zL0(05ibNk+UR5>CqUoU2*c`3MOVF3g9pXr`sl|t9>y-GOUg!O5LAx&R~m)|oc&vLPH^6}z3`lfff z`+r|XD_U|JP(40=G}N)8sFIA^CQL55Y9_)v_3&xPPau*yc?RhtvFyu)D(oDKqG3sQ zC4j;IWKaraL-!+`Vm45ab!`1IyQ}4X%lIH$9v1ToI56WO>+kfZd9J316z6^SFcJ%r zprGK>C^yNjDqb!2V+gUgc=V5Bu!o&mhmZ@l)gg`WftH#yyN*PaFR(Y?D8T;bAAlC+ zG3sbTJJ^NS`7izlk#4ULsX!J&4n_8u+bPYHd1jfTdkx%JEXxRFl)0S?M%c(0{T5Y@ z7%m%y<1tT{Hd|HyY_XGf2g>Q6f}cgIj=ZMdo^er`;j_`_v1v^P{Bn3h2m+vfE2PY+ z`dLcBAe;|iZVJJND{40m_fMlQJ4~CB z7r6sJ)ZP;WnHgGwu9i-pKRMA5e@Q>Q8(W9DHeWV4yyE#*W<>7Nm{PsktAOsr_xi?} zDIc{cx-sq`x>{$aN}S1hzS96%AaF$t2XXgvPKCI2Bo(VX0=?EQM2-*W2_&@nh3?#y zMD9y~goW9HQ=! zVd1G#-H^}Ilg_!FdySK|{Ef4|YHXroi@H83;;XBJ zDIHFmwL?6!&Oq-T(^~Z#9g0GjM3k7g1JV>w2ZW`f2Qwn~6Jba$AqRsX2z0KK!2u*^ z40vZYVbN%N_9E+DPwjP40~v0eMz`8DFVeeGcaqrww;&!FFx$apYmF8u%PE6uei@%_ zerYxvU1?6r+@!t3N>_M-(QLu%5wif^4{@J-h8(#0&x>OyvxnP{t+0%112WcwxXeQ@ z6U9-|OH^$Qnei2fqxVD2F~5V8!@-90(32jZvSR+3Ad8fI<=%p|!YwBu!v93^{BP>q zWTZOz=v0J)@pXc`Psw>&c4ddFVM)$1LIONXD3_4gVX6hn@ z&#rde5~I&sc?@t~75z|_R{bV0KIWf&+5H17tZ{?yEmmrRJ3hQ*1O8+%Dr#Z;wR0sq ziEULw`DxINMQZ)zG`7Q`Nlqqb&dFmqEfiNHxm}*vZ8@kEM34R#3Ms4Q$VKi zA-*ISXrYCTSLmb|%SlW?TJ**HUxY{*BIIn@vHj5(JK7R_o$ENAh9G>k^j0PD*XVXO zk%x9_k~K}OPxrgOM9vwAMynfh76}x|F;Qk%H2o*}yWjOtUL?qDKP2>2)>s+$*~MLY zy?3p>Sm&-)BIzu6(eLnQr!6o@kt_Tf&`Q5O9nfVZ7Wj1^ltMA;=t8w9UKf+LKl4FQ zw!Bn|zGv|R9-7j$U$G_~dn>>OTL|ImCShH~77iRGK~9!ZnzU$&f(FbNegjX@u;cI+ zf}P?Yv`r5eM>A-Winm3DUCx7EMYm=IEg7I}oryM8=p@ON38G2vf}`A-$aFI@iWH?d z(fAgRcZA)UOV|4^ZC%b{nHWF zv|8qY&usOOfJmEXb=s4+-DJt@4evxq2lt0pkeMkUi)iwV`!wPTXj3PXQBbV8w2_ej9RRAg^NyQc zvD4+8TnIXC^I(c-cmzIC96;HW7t)`lNxk8X6gb1A#b?Q=k}jz84lQL!t*@#gICGRb zWi26aL9X0flP(Q7 zWpa<*Ja%==XG`3D$d7iOPyNsT0suqJYoH|y=Dw%eso;TQ{K{e-fVUeP`n88n^0M(~ zuZcH%?tm}8lRM(q6597wPVZPsLyyWM&eOmUF1{8l6SHp z4w%V{?Ge7nS#lHzD=~5bGQzNON-TIkHCa14eiRp+H+z1!)FHG2EIY95ODY+_!}cLj zJ}VaZy+MfPxO1=~PoDNw3L-*E9$u+JgW)E%6xuAsU|FG%Ttr=1E2JBUbo)c%34Bf--ZFdr z`wQQuwK36lm#-=G^CBNaIJO~oa@e%;3@KWiJj30{TWGr23jbD*(Y&Jjw~nhUdBxz5 zybdj0wFkW|jfH`W zF1N`y-MmCKdcQ9AiJSK}Ujw>p_6RMMvVz=C6E{SSMtv+eBoKe@iP1*FL}~g6Ew09! zFl|m3+72yKq(&+faPZ042G)i4Ttza0GyTmdH*J~pm>lFH#^&BLQ$Q)jG1SyBG}L{I zdz?GPB5x!DJ{wR{bjiYml^9P6Oi=se6uU(M9fwYh4>mmnE$mWoKv26=8z2oMrviku ztV|Vd0baz^Uh-LJmtG*9)(t(r2P8LQ^4KzzKDRPad)KG)pyKz-q@joNFxV?x%PhCG z!m9lT)h2s-h79fgfb?9o`d2IG+sRXagGYItPUEBLBR%af1YMLNAqzod|g#L{+ch{iTw&Bn;1 zOwjewK&8t%!^f3K5iE29#>OV`w?#h@{<-75hJl#CjL&fjHnV-8gpZc6GPVKhfn<0v zu1pf2I*rC?wJyvAFPH$029-&JGYN-;g|Ki5*!)^S^}3gyi{Z|ND3f&W!msvC&ZHcF zv>%q`yT%dt$I*0=T0fd1#)_NTw+Wk=qkLU$N0n!&`s!sLv4~saZN-Q3_D?R>r!#^b zHr04kx)feP?i&2RA?7Mv%-%0CDl<*R8U4H%@2p;Nh)6CNZr>ga6bVeoR&CnHBFU)8 zj(*;0>iwt>Ik}&K?4(5O@;PugbmJN}uWpU6nvunBz?F73wW3iP~gRY|p=Pr1bBYd*t*ZO#GLk#LC$`MNPl{a5>2 z6I(cOva+0NC9z;V{N<-_FTOv`JO}=&Q}xGWWlbKAi+clltFjh53OS7`XR`!jc#${XH zd=DQ&Pf*875wsY3g(;mR@@@?2BqtH84?WzPF3b2*ri#*%Uj6RIW2e)GBDn*c`^oF2 zbAVr#0|*2-Tz?d>*E$*}s29-gX0g%xd0|tW5aS|=Nce#1RoU&TTEem1d|dxcJ%5(p zG->kFOid2|x3@y0qUc&r+uYg7zfjl|T^+vDs&#NTYxyt4;j^hZz5(YEPmN+XG2pe2 zc!9pww-bC#&}KhOef>TUTDn~ltrC`7jS}~I-5(SNcS*xdP`?-ri&pLg6Zh)VpoYeU zg&{{EA=WrVc(BVMfK7c`fMVI?XWEUVfKT?x!+T9obH%D;V6A42z<>VfkkNWQrKju# z`C9ROqn%k0x`$tIaT^z;9z=G_rb|1yO<>FiDBw;R64R$9l~+>Ca=PG*yb~)(^;vE>)vL&SPfbBIFcEzK4@_c-KPjn za~Z~-`ejIL%&*Z0^xS+wyVdqu#wp8$|WddSv7iw&V}PoKp7+% zpi{#WHcrU6uh38u5|L3gl7y@WutL`?@Dz_sllEEdre(8&k@N_#D6x`}l7tZWT*Dk7 zq7>}?Fevh?Qjg_jzBQby@-f zLUGCi!)@4X?diTz6jimIeK%suk?^_x-J3}mF8Z;&JUb)3z(3hjOL&eyaTjlKiFr>r z8_unlGon^rF5gdw2`~rr07Ln1EKy1MKq*b7&ZH6q;czKQ`m#%G09To_1Mn{OJEuWA zD1OxP*Uuz+YN$#ul}!ghWbR(_J>ddZ|}tKbgew{t@RI9^}$U-uG&o zF!#@bH;+Mb=YozG$G=KjTzF}%bXRCLwEc+DJzX0jmW}i1%s@l<>03v}>ph~q)yn&d zLl-UrDa%WE4#>RXhS17_i26)>0qW4)6sO=>7_^QrRTG>S1r1>W+yKm}k;WKpOo>8( zv+FSc7Wl4Poc6#-9=LkmPH|X9D`+YIiz24ua%O&<`G$cE=?y)=5d>Cjlo4BuG$z5C z&}QPKVbzaP5%{-U!OBW@e3@l^KlN%{#gi)M8Rk~;qM>SVKKA<4k?oK1Lz_{(w;wc8 z4E}xgP5XYxwVnIN_Dd%h8~fi4|A(BCzgHf7Us8Qf1$!8UiK~{nTgh7#71?SuxyWGF zh&H|4#9d^j{M+{u$pl+61=-*gkTq%tB^`>w+8k7HG+xpIs)QQc&r}Xlu$#8fM&h<( z6Eg@EQ+lny>1{|Y`P;vLz_>Pui5ntIaQ(mhKTRmwjMvbTvOs@(LZzQuM23#V3O??? z{QvK6dV^Y~E;ST3-NdS|#>qDquSIARjf@p}#W>LvGV@3rB^f8AxkfA|`F6LO$&WJM z@o!Vd$xh37usv^R%918}b`%`F&bl?8v^s5_nTYiNj0pB>%|CCT-?T2>gk<<{K}f6Gd@Q-{D4MWDHGg7QI0~0ZNAkz^xXq7e)*FyF&XTka5Wc zATUsJC_D__f*C9q0xe|BPXzs5+uCxNI#)KqC3QcQ$xFFoIL?u?KyuSk<`F5QR?xCj zV#^`a!W8PKms)x~QiTQNytTLJV-uB2rZciE@I2LgY=26Bg)8JdXy0bZ|9Sgb$-(b^ z?VO*;guwK)B|8teL(Y+OM&3m3rK?ZD;Q{izWp&k(-_7c=71>-IEjHb4XTPAs&Sg8T z$jp}@>;a&pV{`gJ##c2^iKD=XilYE#6G==35@6{Pr|K!`=)WqXkT%6ZDK_9fW-!n% z7Ain@IH>|9H3{AHDPv)&vk^KqwT=#>Momr88OPZ9DEJK>3K)J8F@QBP3F6WFwn3Ho z-qwat2lp2tErFSrKzfng>6fD`#WZ>}QTnhX$Gi(o>*_K`ASs1;4W@AHWz8z+tjV89*#r2A+~oia-5%F)S7POpgP&309p z0TKww@O~Hb59VsVx~cnG&pNBlsl9j2F@u+qa8l$*xpz|xb*0fLU%Jn949r;hOJ>&B z*Ctn72ehcPW z-@i{9;#gaiNtwebx_T9Ps#TIIRT>Ei`iUhUV8ofE#%HQ9PbX(NzUS9gNqIpfdRI2` zg_^cp&Po?ypfU2bCQkM*!}9v5@)_p3xNgkfXfvq_#BEhWWn*IF(%ET4OTtLh2T~%O z?McgrR-BTAMiYjN^J$QL&Rd0Xh=}5um`Fbu{#XC$L(l@eho)471R9h4{9T!I^`&O< zFTwMwec~mD9n|b_@hAD3FW8M4%-U)G$H_a%yODg>YUN*R-fDhl_%%SaeJ=6u3{g&Z zF2QQuIyJFS38O`f3gDB*^J&%a+-hXHW>#L(bWE&R2IYAjpF8p1e`_8)q_SGAm7mY; zkHv!uG6O_s3qghNxk|c$ZAETI^LEI*H+NIp#+1H&SWtu zPGxDE^=ueRMp&P6$FMD-Nb&Fp8GfwL+)(rpbZixc6*zmPsv?7-Fqt108AQvN^38a9IBJrG&G2?>Te&dh zpBDW@yEdhM!}S3H5$W%RxpN9hCkoYD+yh&fcwQFS?!B;)~lAvy#qH$TIU*wLVRq zs*iwg$_Q8)z>OO34Y230+CjzF04FoL&k?p+IEW&7p^eI+X%(oh1+uxWn*7>cF8R$` zfu*$Is*;$E1!PR>a=gAhG^UVJ$FOrIT<&hAgy)O9v`N>!yB}CY=(n5A9DICSDGhK; z>zvShyjcIceIzu{xP4vHQTi{w+v_iAV>!YprP-H#yj)8ZM3Oob{2gdJ~JBz&^VCuBwDGHB$0` zRN{4mWP2`Df>DEj7(~Mjnl@Qtw#5Gk!69I((nn%s7#T|*?my(Gvv@h$pQ?^NcK^y zdt?a|N;?0(LX!_jojS!-l7|+NkkF^;r4nfA;bS10vFrQlXmkvjC4RlBsoi@5KV9e) zA9mT&E=o*`>2_6CaYS`9N0L;Gx47~3GVfojOxd?i7Z=z7h3dlH$j~W8Kl@4%hK;N6 zV~{`68dSQVqnlx0Kts_Yp&@Rt930F&qzj2qAojpG`Z}`06EPsO-I|OD9EyTRL>PjP z&dCUF(Wpe0>k!_FDyj9j3~R#z>e`5LNAwOkyvZcVZcFrJQMPk3HZ*cIQ)v*cU{(i; zaW`vZ)E7xyh)szg!#e6TH??lnZ(YN)^(IycG{CcA;oW;anfPEz_>K5tEFl1g^k@|;3_)v#A4SP% zCF9mtDvQj-v}dmoNe)=M&=V}v&T!LqEfkUV4lpEVMYdx6BOWvM5(0zx^J_Wc+)}q+ zlVD@eA5aF&})IKHzk5e(o@d+nPI^v%1}AlUAfAyP(UD!*w82s=ly%9&JoOe$2S*F zKX)gl1Vm2|+S=Bq?NOgrnp?9gh^l4Z!%+pI_6Ud6{*V&jM37)-DSttVPEe=iKuuQN zR?{=zC^I36-ich>*ZcfVa*p0`4QTNjkYSagQ_Qpy&SIp9O;{WHp3JT*9N5NYb#Y3n zNhtm%+*VN3G{%)c7M1*U3ab&-QItN!y()C^cotE5W-y-8t!STjnYseU8ns5HhBP~v zP}5U2l`LsYLR$Z}h!M345W4T`Sm393hH%KBaKV2af_F4j8Jh?(k7&a~EX$ZbYp1e4 z?iHAWx$euppZkbn$4$k8sj>?#v|aMBF>NBEMx*2xhtW-Z{;U7)NT|>5LsO2l3sqOu z`i@aCx($oCh^E=NP;sV;@+8Z!8olnSf>rrs8oLg%tPg#Lmzsb3rT%{X8~6nIem~kj zR+L!SF|aY?jriwd?w%(#I;_Wd%f>zfl=Hc3m#Y06}p!nSB6 z#u!72U*qDZMD&(9EJdXJ#l zgh}7+;LDtL5+Wnba~k-)zozTo_iz1OTds2$_O0JM@$n=rw|n6Zv{J-=OB4AjT-7Z3 z@klkc-1n+}drgb-=O(fwd;O<@Tkj7(&6P{CnZ$sm3hxlAdpUbVH%JAIk%}6*9zb8f zGC@Z_p(E4BzIVn?vHh#S;0>xG<8BcNDYI;J5)Bb<2{R32i+?InrLt-AeIb*0qNg(- zwlNr$1&tG{ijYJxN@khC)>O_L_UG3*nrW2O`g@f^Fj|v2D4uVUp5&vl{VG7w4#uo z2@U|rG*#=Wi=t_~%%HSq*Jw3fv&g;{aYG$bsf9)MNE@n3`9}!Oj9aEp4l2?i5$D&+j zKP^bNZ1$w}AqLLyr+V}ZKBUh*H5dl+VkgdC($aU>DW+=6*8j957cU>?CE-nLdXB4C z`8S8I*cfMqREC~43Wv?M_~Kvy^zkBd>-y}8w4 z)|pN4f|viOwTV4o7R`J*4&zY5+M**=z_;eE=61=VEsyVe&fKo;!r>k7yPRyjL>^{U zQ_$>{4;_5#u+cUpnj1{c=V$MMj>|+%i*8Ac!U@Hg4o{HLmWY+{v@!{tKvohis84%& zp+Xwu?EQi|jy4p&rb@t60*2zGi9l4iX{-suQF-nR3wys=l)}nYs$h4bJ6Ab z05x0cR>H@rQJtBOoPyDyM-OTd9&BfCVwscVS6lDI`49165~(r^941sbt%7Ca9f$Hv zv-h?J1b&wg=>`rO*x#zz1r?2XK?K-8Z(HHVW-BV8k=^a~F;8rex-ebv`J#0yz<5wm ziz%oWQ98;_NZ^a25CLobFla#uS5^rTw`gm;cek6MKM2|S7YY~`)ZJ_y{s%`>9iOOD zN*1*ERf-?xZzF(B+MC^cnyAeld7$2@ejA|c?~@|>Ve{{+f5Mei|Auy-q#^2soq0i* z-`vjMiW@xe>~SN;?3e_Wre9Idl(xT>*kD**VYj?P3MnG@ zx@3v23}+Xy0hD$g^eyXj}r9h(NG(G~;dE zL=3mTR!-Cl4?Nfu1Y4`)<-uYqOMYg%X}V^Nia$25XVJZs%ORsSDy^1+3rq8kg^GTnl?3GkzUg-=C~q z?C$SA74lv`9dTd&2@Uc@4}6`pJ_|FaP1`t z;fcLW;1OZ(8N~`=r$YLIWHGEz7eOj{b8fdP*DD^e}YhHspd8R#CHh&VnOt4Zg`r+j7 z#qi%M3n!e%8at2CY8Lm1JbaPzsGZ*qJbMfT;tHoK!5pc76%_^rC66{oq9k=nWp$R@ zdS{3VY4~X6iuhy>2jy~0I*Z=Xq z6N;wc1D$dd9IS4vc67HpGsIfO;5iHp`_o-Kfhcbtx3=pj$h|zaoB8&wec#TJrrm!r zM%(~8MSGsJndzH)%q=z!&px5E%IJHY#99*YDzl(e3N~HBt`XkH|h8N+ZPwgjv)8K9__U$aM;_Llp5Gls%xzvhZQldi|Zxx<2C} zxtmjXVHUE@JlQgA3@*CNNEngvY0aowII-zojAIa+JrG6;F9jn{%bH^F-*eJL*Ph{f zY5v~)_Jk)&HQP(o&O6iggPp=SS6!yl5QGIz2dA+^&z-2c2I(4?p zZpyx(7PGKeAs_UbE=6Z6=P`9cHgsF2@r%vKoIz7BF2pm?gj~%ojYCX7ap+38j38*;vBUxwofHs8l)?t^Q+5e1|5Yi8LMv^4i-A6sSwXxe zY%h(-$zV#u=m^UvBvAI0Xdo+tbhHh;;e9?94wg82{AZb4($D7j%;&p#ALJzTZ5?sA z@A;t@{4ucRCztS#{*HfwLvL4xf41l;xq*XdxR5|g^S*|_r09xl z9mMq`4h=)AnIea!C;|4y7BBu^H@=JTkaE zs)}q}TqdUzsNnNc_-o7aSKBT3H&?%dy!S$73quLHU5-wr9e%oRge?bzBN2_6gr;0X z(;T^OuiCwwl8NNanEa?U4lBR`TPhg((!_D&Qfgi&!{NX@u>HC;y)^)Z@B9-@wC!}u zLwYMkBj=CHti|zSW9(b4+>DA;QQm3hD*j-m$edh9gSGVY7Kh8nceCNR1T;m>Y-|HDKyIUgvAI%KZQ%`d<; zyv>kq9hE0XN{*HS??Um>hVU-G7u<9k_iU_!$phQk~(dJK%W>;fI4~`@wVI736}r3NG4bf2L+#iz zxjOlgcFHui@9$QHr_|ICL}cP=lV^GKv1j4yC8KlIcK>L!Q5OGcxpwVOFY(T;Ril=3 z?lsR7?V$FKjQeQR(p@!iewgKM%Smt1zlR00J?7yv z33i}le@FhACGI9d+?X--<1r}#2}wB^fF~+Pgc>W$zthbq^8@U`dW|{{k37ZFLM5!w zo&19pw~wL}uLAhZXiH^0v@z?%N}h$MM?cu6us|5s8)Ix<{`1h|`e(+r2s7BSJfl-R ziNRaH|9O4uJ8ZM79nZhAH;CLwrh*W<@iXf|KwQw_$*Awvjyp`z*WAh(@ z$D>T{gD5|c(o-mNoR!}w(~V|MxRA~UO9_8^_w=<}gXZ7I&iRMd_v_!e8g7d8^bv{E z1|Q|IXS2Se6!Cx$&=ozq^d?(=4jp!E8H-q4#R1c5V6om48!Ox=ttk>FH6odNJ7wOB zjx!pR?Q?q*Nxb~T*t}`kgj{TT>_$X`)Sh#R!raBTHpARB#-E84F`;}_r9a;119qBB zJW|0OX_kLQ=YE(CHh~ho0fZb84kJVkJ_5)dl?dGwQuN_ahbF8FI`^KB=7^TX~4*qTAfQDzn$SBZ<~?#yK*!u#Qj!Fi4fI5C@Di zSjK7*$y4rg2}k;r`!U+YiL(bFz%l@4xEwzZ7{A+QX{Xl_@lC9L3HW(mnQEgBJ8g2==oS|a!#e}=o@RLBcjar zTKstcVdH?n)JRZ8D4Dgoq^-aNdbs{je(Ile-q!Nn2Ja)KPa5Nia3ywf6Oz2*5X(0T z0Y>UjWU?k)U@6ec(%n%ct(DXXErWIUmf7=?h2^h@U_StjTI{6!XSRBM&_BQjMo z*JWphox-cNvx%}Ozt%YP#T80>A?>NdvS!xz?m=4d^6cvM?LT3L15eM|0m|Rgedh4| zRcKG?C&#Gs2JgheK2y7R6Ro?n_XR|ty9~=~mV-gAR z9ZuuS|J?OSc^zPz1&M{r;VPYugI%jg##!*la>S?*jN&z3|MTL4|(Q$zTA&kUrabMf1)`o>K}*$ z6qbz-w(w_HM@H!-=&42f#D0aW$i6dtlTxKhCE zEL*YuO*ivJw|_Gdu3@zH9A|6%I#*+DVz5MH<%r{}eOb~1H(jiXvYrzb1FB9uOoOdH ze_Crj+iP`v^r9%w?V$qJZ++Oa`D0uLqAj*RBR+?Ih=MbGN-L5mFFWVwS3S?>fGIyl z2)3kC>JtElF*XLa@|k}$!3Z<^y=rdRs&pbl=#7Jo78n5BIcX|XW8);_s8-DmHuC%- z@9ilRdgC8z*<|MNOBW4;u-cu#phJz9ewz8^MI@pK#U6*&BO(rXl=^jPeD#i8rxfBl zHX{jCWh(CVExJt9mlCoW=y4=5d!{+vUMfz`&p#M zm8`~w96~r4A79=t;9gx!KwHnd$6P9#Qxe3qZcUsm;Jp296Z+ z`P+)6S?-*sVIJe{kKDmamN%1k%W`JmL)lEN=R8X>w@#nfnP*Z1r|)OqEoA7SwZQ?pGAJ@? z;VHmsk>!253sc-~EKRA9-HM-52=ST4(*A^}#)nFPX$xrWMZgtA-7JOF5FX^b7>7~2 z(VLM=&qO5aGNwk~C6IGSuh?5J2vwJjma=TA^EjB4)c)iD5P;@lIsBpr^Q_Srzp#x8 zHY|9l|IPg2kS;5-s)|#=8|h6H1UDwBrT(e6S}aiWOwBTLE0}deVZ1ux%o1uhJ~`v0 zEyx^W$VlN3lqbJ1H4=L`A@%HE+G+LmUgHZ_f1P_<`Ur6v_1IJb9UCk+<_7@pm0XkV za1uHhAQ2G8N*gtOvO|JNn4K)DJ}iFAJFm^Sq(PH7nT1;okVf#yZ!UV5TpJB|tw$rhIJAK9+IW&nCEY7F>|)PSO+Volm$j zFbOG!hc}NVWO&9SQ&VC!N5=!cvStXWBS&WGM@5x7x4*=0BE^Lug(}0>B4SIJ7!$ma zQA0kzqmz;89WQ0pMVD8Oy+BNgjA!D{KY?LoP=vC_=$%DoZM0`cvk;ZOa?GJ9gz5XBIDg&zXp0Qs3Hwa+R$0s^Zep|XHIKn%W2 zuMpAU&WqzA!dVKk9QmwA6ycX9#BoZ-c&g6Mig)Ht%*;O5$&H0e1g0bd8HT_}V|jjq zBLqPx-2C+@4Zc=g8QY|G|MCAd1kKb$!I@jovoVSC!j|=&Vc!5Aj}%ATMZ48?CQvLh z|BW6?md-&P+ungZ*`o`v1-)^XE`5!Y5(W>r6uc2G0A*aHGKSviDAk>YH9fKwx97OPlD|<%- zYqE8>3(!n+aZyAZOyLsc3u^Nu;V9`{HH}G@ya?WeJ^!)TF4EK+7g;}?ZLDMs9r4#f z?F5o7e<-`x;#Jd2us2|rSnD+eL^GHfoZj-0C>B0{NU%suN=@*1!nL$UuqJ>kYn;HT z5#@<2ROt1D!elezv|SCVx|Skez4$baus?=q?hqJ}y6{jamNPTzVLIh=avQf}#wd?} zpo*gF#CFv13aOaG0?WvOWuYIzmE9o?N+``VI27u{r+KKMZKJ|DLtjLESQO>O# zmxq_4{VN{Xd7;Q5vXN=1X-$^siCCh!)(z#0{5JkTQIf<~@*C$HRoTx^?mG~W>B_$4 zdRJ%d7`^RimW4t|t|horWRxA}#d5@W=X zh+U+Pl+HGz=k(E168^XbYBw|OWBY?>N;ALV)0VRe`KZVS_-_}3Q1k3_epo>MUfeF# zmEQl%|GxpyyzJ%Ga0PFg8%299qg)KOtMIs+MOClG_FSWY{-l^TaXKEqMvcFN@Vd0S zp9OxGMI6@z85IQdR8%G|cwX*tn25^9srJrW?IR`XrBA40^Ww)5c4xS-rdufPI@L5DU56MXqa!}4u5U`+af4!hgIh>IGEW#-b@-POJQfLUi>I| z$jPQ?XCi8yKr3ZH_L6cGp`)&Dg_da< zp%S8CrlD0RphhAi$$IE8AM~Y14I!oK#9&Y`GCJtXsQK z6Tu!=mq*tF@AR0ywqSuKY*>es6{!-wxC_ZfV#vPB`QaC+Nsricu-Pb|Rz*2INulrY0S66J#u8Q1b$WcErRcVjun{Fnn z7t$V2%{@o`$ABc5O?NL)5}WQBo;TpoJvNMXNKmHLL>1o%Qts%X!C4kQ3eCt>aam!dj=@A}9OTkp0)|6ZQ7~(M3ha?-0IqWsp)?03krI>|%Hc z3m5-oX7YKME03NSU0WP;5RXr{B8tK(Dg`$LKotB`(c;rf^`Wri3<7A_h0>g;U8`{e9l38 z(+D#}SVkoo&aa|R^Fxq-caOEh5n>Xq@7F(?9D(=y6p7uIVNYWJP%D6@(k zT@3^)6tb!*P`#xfuVxoUUZYZn?i2_mAI}T6aR4VAY z2hcj@Mf|Gu?(L4fp#ohFvH1Bl_e&dUr2ovP+Nds@)QdC&YWW_dk;>lCwDzl8fAPn- zf-Uym6&O#|X(Harvi1(8Tg4{|6gQoIC^fD%V=tE)DZbEEwL5&b>iYLp3(~L zz~8e^QnRmFIw{#LG#cycij9ZjNKP-+hmr9(^NFD`t)O7*8P&^JVdbfx4b%8S!UyUo zW7?Kf?5KvHZMb#w>7i8Ug$cLv4$0UPKw%EOY#uR|gI1@R4ACt-)^ry2ZSW^2zId;o zh3tr(7;SknJ~yK`WHhNH9#i5H?XEV>DS>4B>{Tu<37x*r_xCjwPwGll7qmFHTf*&~6IY!i(V?5wmHZq_204Lbv zM48sdBQY4EhTE$6 zkjs=Z3#myhlWNFOed}LIyqfqC*}U#~Dr;EkqF48Yq5KWQLW|`P(;ZP?z7uw&9Oa?%M6GC5}qM#!@-sV7yni}iA_c_Tj zaCZZP?4c`y_~e^sMQfvv$!+4uugdMq9M4K(+tYDu`&|ecX-A6`83PgZD%1dTwX)+a zSG)*@aJyn!jqcgNu1b3=FPH&?)?k^*msj=+ZoeLVI8t zQalK}-+AP2_2DQPaZMMmU^=~cM>F%M^sD#J8Xxp5c+M0jd$-pmtsmG`e2*jZG9f%E z>^?nCvO7G|AUK)vF*_uRSPSCmf~A8iy5V_Z30Y9#{?igb9c-%?M{`rDBH zH~)Hz<2N+ZLOD-{)S8J6+iC+=oX5b*og{t6pyaI|D;%DFEYC?2k-A>*vnJj`rbb2w z96t|)357_9mT@}BMXI2q0;_mfv4p6`?-nFQ)#zrdI7mak*F6ZYnLirFAfqY_V#+Ha zC08e6a$`(qHwEe7ynKSeC@&S}Eg+h+#WjTDTAPtEX-vbMz{C1!#1_)1=!{(>wD3t%I|52AoICO<6v8i7<OqC5 zpI>8NQejW=6vp{ntb&JT%vLfD)RmS`-IMih z^+%C5Ny3akdr+2L#%5bd{Pu#6ii(V}I{}X{e{=#dlEvpas3Ov5bwadYO00Jg7;p?J zP@X>Nm2e{qid}dqVz3Z=<6Vc0gbdim{2|IosihrWs!xNTDIWxhi(AktJqx+F#d6?; zX*$G5Z|Hbxu=_I8>E{yc|e2lkw1}tf~CS#;%V{&sVRqq)p!9aem^1CLWa{n<_%>opbrJ}|h z@E`x@cxdT9vLuQcdu_a;3XZJR26T(a>U~`CfNqQRN|x&AFWaKRu6B$Y<0}S)AS2S~ z3Bn6b+-CeX9WAV>PcCZQE#rau`8ShJ^W+N*Fi?y+F@D0M~b)8U#C ziZklDWPv(H^o3u#SX|+_4t_O}?5+AlTq&42#P_O7bQlPwc@>9zHViB9g*$ccy!)Tp z5LB4vLO55G5XFZp6-s6ijL-vSo8p=l#v5VfJs1ausXH$f9+7x<7+}bQ-bh)httZvC zay!*_F(J*i9CcG{TMh5xPLNoe^N$)ED(SE#-S6x(1DQzJvlhJBaF7z0MTI}3?y&-pxu&!5EF)FX zg)Ou#!ba(|Nw-+efxNXhq2vjhUHC0y_j2q}d&xL*%U_TPzYw+ZqK3#TB8XG3LAeS7 zFQeU4C?wpfElQ5v=I_HBlP|CnYlDK-nrUr1)~Si0=?^?qH|75nJT(Pcx?QpIO&Elo z7n*{C@MEuP zzISK{A1TCZ(xc!FPXYy)kxLTzi?K;q(E^oM#f?~F!DL_1#m-^n0<$&=HK|((NU{gM zQc`V($rEeINz4@e6yoFt8L?1s&<0Do~1|TxZSxXl6l&)K&_w+05 z)I(-s^(Gf%TlPuOB4ui~X1Kqd_g>9|W_`jg3-0SP9hjgfVT@HMB@;R-mmGibZY81F zvijJDoR5sH#E-+anDBEVe3V80XUJh7g@YE|1P?7WGfyP0I-dOPTi#kFng%vG|NFH1 zdhz8zt~+N(^{)kuc8ddhj~AU^Z5`{(AM!lUn|iL?qH$zO8`Hr5@qdc-;{W=b#vug$ z$ZN>&7?o|nzlb~yUYxw>+F5^@|F3np)6 zd1WC~XaxvPeaq}0FV>^EU*Kj}SX=#ITb?CR9vNuRp;gltJV^*Qss_wPoX|qhh#@FK zVVKGSXqFJ!*W<)oSPV-~)afkK5-b1OMa6Tue*}P1Ct2+ueWA)$%&C+TFi}NOzQI&Z zfq$Rz4mxrLx_TZYz@StEZm5R#qpx~g6))uip0Jp1XVU4@*%aG^{5XkY+kEEZvA~bG zQzek5MQu4*@_M80rxE`1iH9GElha>v($U{f`=!(+9FzUse`y&7m6TZPRkt@Vzk$FFfRfWz%+ z6X(r_5BVjU+RDV3W!4=!s&(GNSrTLB%B4by`%ykG>mQ1QCT0!EruL5k#>UlR$6F5R zc$MDY!Tkl!O)7#^2$Tat)=>1bp!BoU!HeeeV?z~YNw9i<<<49=0FRE@$!01G90n~; zPE<;)4#Zpvs`gbOlcOfAF>s>r)c#+)Lt`haZ^Et8_yZ^k8lW&98D# z?ZnW9wtnUNwn@n4c~oqhZD~}vYLDiy(`p@8WQkwxm-e&CPxR2a=lUDjR< zYdME_!w*A0LgjY7Us3^+ka4h?=4~eH#2D9- zfqt~~xcLrS({xt!)Vp^a%1xzY?8(#hFJ?)KY4}ApZ)WQT-8Y{fzxX7%AmBn^Is9W+ zHq_I=pA79|;P`{1^o&o@xi$o^M+o#E|Cfnm;Xa?!R0QFzSL4EtQEue6%g8VJSI`l0 zi}enw-##h8)$*=USrQh|AQtZ|c3W!{FCv{+B)6`~lZ_>2qTL`9ILu4Q^9tKeQe@LnfL}ii`yik;bWqb3*E}8VV3zelS??f7k!p699}m zO&%htc4=47M~rULRJy4o+cAZOP1x%hwtW_IOi-{u2YmKZCDv`RN~Xb|O=4VAWTTQ2 zw*uub@FsGAhKE)B)6z&tNLB;vY-^K1N|ZYmgfHnMWwD^6unIjB2y21ntB(0{<=G-V z)|Af{<#qq$!&1e76DY)G9X1QHYH2cz#Q*URCAgdQkw(FV>{Y@(Jz9o$0su(m004jy z01ym$`5ayV7g^QoUCKw%;jmP3&sgHxIk=Vl%jTs*T-)%SjTofD2jNRU@ItKgA`rRA zNFYz$qm-8~4G=H>JY;?v<$^PrUU+SDHj|hdbizt!wk}OVLyfA{OKe4cs`_?}^WF>; zWu|ki7B@&J%oCghBqT=XNW`Gwc> zY0G)Rg0~QKBvxFf+Fl_Z3#($TIpQMS8lUXv5BJM@jzN07$#6olm&oth+JR-g=e*T7 g=MZ$VKMDTtFP!Qw&oAZ#YX2|pwg0>RKRki|16*8x`v3p{ literal 0 HcmV?d00001 diff --git a/js/mobile/babel.config.js b/js/mobile/babel.config.js new file mode 100644 index 0000000..4c12ec8 --- /dev/null +++ b/js/mobile/babel.config.js @@ -0,0 +1,10 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: [ + ["babel-preset-expo", { jsxImportSource: "nativewind" }], + "nativewind/babel", + ], + plugins: ["react-native-worklets/plugin"], + }; +}; diff --git a/js/mobile/eas.json b/js/mobile/eas.json new file mode 100644 index 0000000..74c2c95 --- /dev/null +++ b/js/mobile/eas.json @@ -0,0 +1,40 @@ +{ + "cli": { + "version": ">= 12.0.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": true + }, + "env": { + "EXPO_PUBLIC_APP_ENV": "dev" + } + }, + "preview": { + "distribution": "internal", + "ios": { + "simulator": true + }, + "env": { + "EXPO_PUBLIC_APP_ENV": "dev" + } + }, + "production": { + "autoIncrement": true, + "env": { + "EXPO_PUBLIC_APP_ENV": "prod" + } + } + }, + "submit": { + "production": { + "ios": { + "appleTeamId": "RK9WWM4Q99" + } + } + } +} diff --git a/js/mobile/global.css b/js/mobile/global.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/js/mobile/global.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/js/mobile/index.ts b/js/mobile/index.ts new file mode 100644 index 0000000..f45fcf4 --- /dev/null +++ b/js/mobile/index.ts @@ -0,0 +1,5 @@ +import "./global.css"; +import { registerRootComponent } from "expo"; +import App from "./src/App"; + +registerRootComponent(App); diff --git a/js/mobile/metro.config.js b/js/mobile/metro.config.js new file mode 100644 index 0000000..b0963fe --- /dev/null +++ b/js/mobile/metro.config.js @@ -0,0 +1,6 @@ +const { getDefaultConfig } = require("expo/metro-config"); +const { withNativeWind } = require("nativewind/metro"); + +const config = getDefaultConfig(__dirname); + +module.exports = withNativeWind(config, { input: "./global.css" }); diff --git a/js/mobile/nativewind-env.d.ts b/js/mobile/nativewind-env.d.ts new file mode 100644 index 0000000..a13e313 --- /dev/null +++ b/js/mobile/nativewind-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/js/mobile/package.json b/js/mobile/package.json new file mode 100644 index 0000000..dce214a --- /dev/null +++ b/js/mobile/package.json @@ -0,0 +1,52 @@ +{ + "name": "flowy-mobile", + "version": "0.1.0", + "private": true, + "main": "index.ts", + "scripts": { + "start": "expo start", + "ios": "expo run:ios --device", + "publish:ios": "eas build --platform ios --auto-submit && echo 'Go to App Store Connect and submit the testflight build for app review. Visit for more information: https://docs.expo.dev/submit/introduction/'", + "android": "expo run:android", + "compile": "tsc --noEmit", + "lint": "expo lint" + }, + "packageManager": "yarn@1.22.22", + "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^7.0.14", + "@react-navigation/native-stack": "^7.2.0", + "@tanstack/react-query": "^5.90.21", + "clsx": "^2.1.1", + "expo": "~54.0.0", + "expo-audio": "~1.0.13", + "expo-camera": "~17.0.10", + "expo-constants": "~18.0.13", + "expo-file-system": "~19.0.16", + "expo-haptics": "~15.0.7", + "expo-secure-store": "~15.0.8", + "expo-status-bar": "~3.0.9", + "expo-video": "~3.0.10", + "firebase": "^12.10.0", + "lucide-react-native": "^0.575.0", + "nativewind": "^4.1.23", + "react": "19.1.0", + "react-native": "0.81.5", + "react-native-gesture-handler": "~2.28.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-worklets": "0.5.1", + "sonner-native": "^0.21.0", + "tailwind-merge": "^3.5.0", + "zod": "^4.3.6", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "expo-build-properties": "~1.0.10", + "tailwindcss": "^3.4.17", + "typescript": "~5.9.0" + } +} diff --git a/js/mobile/src/App.tsx b/js/mobile/src/App.tsx new file mode 100644 index 0000000..f932e26 --- /dev/null +++ b/js/mobile/src/App.tsx @@ -0,0 +1,40 @@ +import { useEffect } from "react"; +import { StatusBar } from "expo-status-bar"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { NavigationContainer } from "@react-navigation/native"; +import { + initialWindowMetrics, + SafeAreaProvider, +} from "react-native-safe-area-context"; +import { Toaster } from "sonner-native"; +import { createQueryClient } from "@/lib/query-client"; +import { PusherProvider } from "@/lib/pusher-provider"; +import { RootNavigator } from "@/navigation/RootNavigator"; +import { useAuthStore } from "@/stores/auth-store"; + +const queryClient = createQueryClient(); + +export default function App() { + const restoreSession = useAuthStore((s) => s.restoreSession); + + useEffect(() => { + void restoreSession(); + }, [restoreSession]); + + return ( + + + + + + + + + + + + + + ); +} diff --git a/js/mobile/src/api/client.ts b/js/mobile/src/api/client.ts new file mode 100644 index 0000000..2095d79 --- /dev/null +++ b/js/mobile/src/api/client.ts @@ -0,0 +1,270 @@ +import { appConfig } from "@/config/env"; +import { useSessionStore } from "@/stores/session-store"; +import { ApiError } from "@/lib/errors"; +import type { z } from "zod"; +import { + BillingStatusSchema, + CheckoutSessionResponseSchema, + DepotObjectSchema, + FirebaseTokenResponseSchema, + GetLivekitTokenResponseSchema, + HumanSchema, + ListInvitationsResponseSchema, + ListNetworksResponseSchema, + NetworkSchema, + NetworkUsageSchema, + PortalSessionResponseSchema, + PrepareUploadResponseSchema, + SignInResponseSchema, +} from "./types"; +import type { + AcceptInvitationRequest, + AddMembersRequest, + BillingCadence, + CreateNetworkRequest, + PrepareUploadRequest, + RequestCodeRequest, + RevokeInvitationRequest, + SignInRequest, +} from "./types"; + +interface ApiClientConfig { + baseUrl: string; + getToken: () => string | null; + onUnauthorized: () => void; +} + +class ApiClient { + private config: ApiClientConfig; + + constructor(config: ApiClientConfig) { + this.config = config; + } + + private async fetch( + method: string, + path: string, + body?: unknown, + ): Promise { + const headers: Record = {}; + + if (body) { + headers["Content-Type"] = "application/json"; + } + + const token = this.config.getToken(); + if (token) { + headers["Authorization"] = `Bearer ${token}`; + } + + const response = await fetch(`${this.config.baseUrl}${path}`, { + method, + headers, + body: body ? JSON.stringify(body) : undefined, + }); + + if (response.status === 401) { + this.config.onUnauthorized(); + throw new ApiError(401, "Unauthorized"); + } + + if (!response.ok) { + const text = await response.text().catch(() => "Unknown error"); + throw new ApiError(response.status, text); + } + + return response; + } + + private async request( + schema: z.ZodType, + method: string, + path: string, + body?: unknown, + ): Promise { + const response = await this.fetch(method, path, body); + const json = await response.json(); + return schema.parse(json); + } + + private async requestVoid( + method: string, + path: string, + body?: unknown, + ): Promise { + await this.fetch(method, path, body); + } + + // --- Auth --- + + async requestCode(data: RequestCodeRequest): Promise { + await this.requestVoid("POST", "/auth/request-code", data); + } + + async signIn(data: SignInRequest) { + return this.request(SignInResponseSchema, "POST", "/auth/sign-in", data); + } + + async me() { + return this.request(HumanSchema, "GET", "/auth/me"); + } + + async signOut(): Promise { + await this.requestVoid("POST", "/auth/sign-out"); + } + + async getFirebaseToken() { + return this.request( + FirebaseTokenResponseSchema, + "POST", + "/auth/firebase-token", + ); + } + + // TODO: security: require passing in the particle id once api deprecates this + async getParticleDownloadUrl(objectId: string): Promise { + const response = await this.fetch( + "GET", + `/particles/${objectId}/download`, + ); + const data = await response.json(); + return data.url; + } + + // --- Settings --- + + async updateSettings(data: { + email_notifications_enabled?: boolean; + }): Promise { + await this.requestVoid("PATCH", "/humans/me/settings", data); + } + + // --- Depot --- + + async prepareUpload(data: PrepareUploadRequest) { + return this.request( + PrepareUploadResponseSchema, + "POST", + "/depot/upload", + data, + ); + } + + async confirmUpload(objectId: string) { + return this.request( + DepotObjectSchema, + "POST", + `/depot/objects/${objectId}/confirm`, + ); + } + + // --- Networks --- + + async listNetworks() { + return this.request(ListNetworksResponseSchema, "GET", "/networks"); + } + + async createNetwork(data: CreateNetworkRequest) { + return this.request(NetworkSchema, "POST", "/networks", data); + } + + async getNetwork(id: string) { + return this.request(NetworkSchema, "GET", `/networks/${id}`); + } + + async addMembers(networkId: string, data: AddMembersRequest): Promise { + await this.requestVoid("POST", `/networks/${networkId}/members`, data); + } + + async removeMember(networkId: string, humanId: string): Promise { + await this.requestVoid( + "DELETE", + `/networks/${networkId}/members/${humanId}`, + ); + } + + // --- Invitations --- + + async listNetworkInvitations(networkId: string) { + return this.request( + ListInvitationsResponseSchema, + "GET", + `/networks/${networkId}/invitations`, + ); + } + + async listMyInvitations() { + return this.request(ListInvitationsResponseSchema, "GET", "/invitations"); + } + + async acceptInvitation(data: AcceptInvitationRequest): Promise { + await this.requestVoid("POST", "/invitations/accept", data); + } + + async revokeInvitation( + networkId: string, + data: RevokeInvitationRequest, + ): Promise { + await this.requestVoid( + "DELETE", + `/networks/${networkId}/invitations`, + data, + ); + } + + // --- LiveKit --- + + async getLivekitToken(networkId: string, streamId: string) { + return this.request( + GetLivekitTokenResponseSchema, + "POST", + "/livekit/token", + { network_id: networkId, stream_id: streamId }, + ); + } + + // --- Billing (network admin only) --- + + async getNetworkBilling(networkId: string) { + return this.request( + BillingStatusSchema, + "GET", + `/networks/${networkId}/billing`, + ); + } + + async createCheckoutSession(networkId: string, cadence: BillingCadence) { + return this.request( + CheckoutSessionResponseSchema, + "POST", + `/networks/${networkId}/billing/checkout-session`, + { cadence }, + ); + } + + async createPortalSession(networkId: string) { + return this.request( + PortalSessionResponseSchema, + "POST", + `/networks/${networkId}/billing/portal-session`, + ); + } + + async getNetworkUsage(networkId: string) { + return this.request( + NetworkUsageSchema, + "GET", + `/networks/${networkId}/usage`, + ); + } +} + +export const apiClient = new ApiClient({ + baseUrl: appConfig.orionUrl, + getToken: () => useSessionStore.getState().token, + // SecureStore writes are async; we fire-and-forget so the throwing + // request doesn't have to wait for persistence to finish. + onUnauthorized: () => { + void useSessionStore.getState().clearToken(); + }, +}); diff --git a/js/mobile/src/api/types.ts b/js/mobile/src/api/types.ts new file mode 100644 index 0000000..1ab02d1 --- /dev/null +++ b/js/mobile/src/api/types.ts @@ -0,0 +1,324 @@ +import { z } from "zod"; + +export const HumanSchema = z.object({ + id: z.string(), + created_at: z.coerce.date(), + email: z.string().email(), + email_prefix: z.string(), + email_notifications_enabled: z.boolean(), +}); + +export type Human = z.infer; + +export const NetworkSchema = z.object({ + id: z.string(), + name: z.string(), + admin_human: HumanSchema, + humans: z.array(HumanSchema), + created_at: z.coerce.date(), +}); + +export type Network = z.infer; + +export const ListNetworksResponseSchema = z.array(NetworkSchema); +export type ListNetworksResponse = z.infer; + +// --- Network request/response types --- + +const CreateNetworkRequestSchema = z.object({ + name: z.string(), +}); +export type CreateNetworkRequest = z.infer; + +const AddMembersRequestSchema = z.object({ + email_addresses: z.array(z.string().email()), +}); +export type AddMembersRequest = z.infer; + +// --- Invitation types --- + +export const InvitationSchema = z.object({ + network_id: z.string(), + network_name: z.string(), + email: z.string(), + created_at: z.coerce.date(), +}); +export type Invitation = z.infer; + +export const ListInvitationsResponseSchema = z.array(InvitationSchema); + +export type AcceptInvitationRequest = { network_id: string }; +export type RevokeInvitationRequest = { email: string }; + +// --- Depot types --- + +const PrepareUploadRequestSchema = z.object({ + network_id: z.string(), + name: z.string(), + content_type: z.string(), + content_length: z.number(), +}); +export type PrepareUploadRequest = z.infer; + +export const PrepareUploadResponseSchema = z.object({ + object_id: z.string(), + upload_url: z.string(), + upload_headers: z.record(z.string(), z.string()), +}); +export type PrepareUploadResponse = z.infer; + +export const DepotObjectSchema = z.object({ + id: z.string(), + name: z.string(), + content_type: z.string(), + content_length: z.number(), + contains_content: z.boolean(), + created_at: z.coerce.date(), +}); +export type DepotObject = z.infer; + +// --- Particle property schemas --- + +export const StreamPropertiesSchema = z.object({ + name: z.string(), + description: z.string().optional(), +}); +export type StreamProperties = z.infer; + +export const FolderPropertiesSchema = z.object({ + name: z.string(), + color: z.string().optional(), +}); +export type FolderProperties = z.infer; + +const TranscriptWordSchema = z.object({ + word: z.string(), + start: z.number(), + end: z.number(), +}); + +const TranscriptSentenceSchema = z.object({ + text: z.string(), + start: z.number(), + end: z.number(), +}); + +const TranscriptParagraphSchema = z.object({ + sentences: z.array(TranscriptSentenceSchema), + start: z.number(), + end: z.number(), +}); + +export const TranscriptSchema = z.object({ + transcript: z.string(), + words: z.array(TranscriptWordSchema), + paragraphs: z.array(TranscriptParagraphSchema), +}); +export type Transcript = z.infer; + +export const MediaPropertiesSchema = z.object({ + object_id: z.string(), + mime_type: z.string(), + duration_ms: z.number(), + size_bytes: z.number(), + transcript: TranscriptSchema.optional(), + source: z.enum(["camera", "screen"]).optional(), + // Set by the particle processor worker once an iOS-playable MP4/m4a variant + // has been produced from a non-iOS-playable original (e.g. WebM from desktop). + // When present, clients should prefer these over object_id/mime_type for playback. + transcoded_object_id: z.string().optional(), + transcoded_mime_type: z.string().optional(), +}); +export type MediaProperties = z.infer; + +export const FilePropertiesSchema = z.object({ + object_id: z.string(), + filename: z.string(), + mime_type: z.string(), + size_bytes: z.number(), +}); +export type FileProperties = z.infer; + +export const TextPropertiesSchema = z.object({ + content: z.string(), + edited_at: z.coerce.date().optional(), +}); +export type TextProperties = z.infer; + +export const QuestPropertiesSchema = z.object({ + title: z.string(), + description: z.string(), + status: z.string().optional(), + // humanId + assigned_to: z.string().optional(), +}); +export type QuestProperties = z.infer; + +export const PaperPropertiesSchema = z.object({ + title: z.string(), + content: z.string(), +}); +export type PaperProperties = z.infer; + +// --- Reactions --- + +export const ReactionsSchema = z.record(z.string(), z.array(z.string())).optional(); +export type Reactions = z.infer; + +// --- Tombstone (soft-delete) --- + +// Fields added to non-container particles when their creator deletes them. +// We keep the doc around so concurrent viewers can see a "This particle was +// deleted" message in place, rather than being jumped to the next particle. +const TombstoneFields = { + deleted_at: z.coerce.date().optional(), + deleted_by_human_id: z.string().optional(), +}; + +export const REACTION_EMOJIS = ["\u{1F44D}", "\u{2764}\u{FE0F}", "\u{1F525}", "\u{1F440}", "\u{2705}", "\u{2753}", "\u{1F602}"] as const; + +export interface ParticlePropertiesMap { + stream: StreamProperties; + folder: FolderProperties; + media: MediaProperties; + file: FileProperties; + text: TextProperties; + quest: QuestProperties; + paper: PaperProperties; +} + +// --- Unified Particle types --- + +const ParticleBaseSchema = z.object({ + id: z.string(), + created_at: z.coerce.date(), + created_by_human_id: z.string(), + updated_at: z.coerce.date().optional(), +}); + +export const ParticleSchema = z.discriminatedUnion("type", [ + ParticleBaseSchema.extend({ + type: z.literal("stream"), + properties: StreamPropertiesSchema, + // e.g. ["human:human_xxxx", "human:human_yyyy"] - visible only to Aron and John + // e.g. ["network:xywx"] - visible to everyone in the network + visible_to: z.array(z.string()), + // Marks human_id to their `playback_position_at`: where they left off in a conversation + playback_markers: z.record(z.string(), z.coerce.date()).optional(), + // Timestamp of the most recent child particle + // used for sorting streams by recent activity without needing to query subcollections + last_child_created_at: z.coerce.date().optional(), + // Array of humanIds currently in the huddle (updated via LiveKit webhooks) + huddle_active_participants: z.array(z.string()).optional(), + status: z.enum(["open", "closed"]).optional(), + }), + ParticleBaseSchema.extend({ + type: z.literal("folder"), properties: FolderPropertiesSchema, + // e.g. ["human:human_xxxx", "human:human_yyyy"] - visible only to Aron and John + // e.g. ["network:123"] - visible to everyone in the network + visible_to: z.array(z.string()), + }), + ParticleBaseSchema.extend({ type: z.literal("media"), properties: MediaPropertiesSchema, reactions: ReactionsSchema, ...TombstoneFields }), + ParticleBaseSchema.extend({ type: z.literal("file"), properties: FilePropertiesSchema, ...TombstoneFields }), + ParticleBaseSchema.extend({ type: z.literal("text"), properties: TextPropertiesSchema, reactions: ReactionsSchema, ...TombstoneFields }), + ParticleBaseSchema.extend({ type: z.literal("quest"), properties: QuestPropertiesSchema, ...TombstoneFields }), + ParticleBaseSchema.extend({ type: z.literal("paper"), properties: PaperPropertiesSchema, ...TombstoneFields }), +]); + +export type Particle = z.infer; + +export type ParticleType = Particle["type"]; + +/** Container types can have children subcollections */ +export const CONTAINER_TYPES: ReadonlySet = new Set(["stream", "folder"]); + +export function isContainerType(type: ParticleType): boolean { + return CONTAINER_TYPES.has(type); +} + +/** True when a non-container particle has been soft-deleted (tombstoned). */ +export function isParticleDeleted(particle: Particle): boolean { + return "deleted_at" in particle && particle.deleted_at != null; +} + +// --- LiveKit types --- + +export const GetLivekitTokenResponseSchema = z.object({ + token: z.string(), + server_url: z.string(), +}); +export type GetLivekitTokenResponse = z.infer; + +// --- Auth types --- + +const RequestCodeRequestSchema = z.object({ + email: z.string().email(), +}); +export type RequestCodeRequest = z.infer; + +const SignInRequestSchema = z.object({ + email: z.string().email(), + code: z.string(), +}); +export type SignInRequest = z.infer; + +export const SignInResponseSchema = z.object({ + human: HumanSchema, + token: z.string(), +}); +export type SignInResponse = z.infer; + +export const FirebaseTokenResponseSchema = z.object({ + token: z.string(), +}); +export type FirebaseTokenResponse = z.infer; + +// --- Billing types --- + +export const BillingCadenceSchema = z.enum(["monthly", "annual"]); +export type BillingCadence = z.infer; + +export const NetworkPlanSchema = z.enum(["free", "pro"]); +export type NetworkPlan = z.infer; + +// Mirrors Stripe subscription.status plus "active" as the default free-tier value. +export const BillingPlanStatusSchema = z.enum([ + "active", + "trialing", + "past_due", + "canceled", + "incomplete", + "incomplete_expired", + "unpaid", +]); +export type BillingPlanStatus = z.infer; + +export const BillingStatusSchema = z.object({ + plan: NetworkPlanSchema, + plan_status: BillingPlanStatusSchema, + cadence: BillingCadenceSchema.nullable(), + seats: z.number().int(), + current_period_end: z.coerce.date().nullable(), + cancel_at_period_end: z.boolean(), + price_monthly_cents: z.number().int(), + price_annual_cents: z.number().int(), +}); +export type BillingStatus = z.infer; + +export const CheckoutSessionResponseSchema = z.object({ + url: z.string().url(), +}); +export type CheckoutSessionResponse = z.infer; + +export const PortalSessionResponseSchema = z.object({ + url: z.string().url(), +}); +export type PortalSessionResponse = z.infer; + +export const NetworkUsageSchema = z.object({ + plan: NetworkPlanSchema, + used: z.number().int().nonnegative(), + limit: z.number().int().nonnegative().nullable(), + reset_at: z.coerce.date(), +}); +export type NetworkUsage = z.infer; diff --git a/js/mobile/src/components/Avatar.tsx b/js/mobile/src/components/Avatar.tsx new file mode 100644 index 0000000..bced118 --- /dev/null +++ b/js/mobile/src/components/Avatar.tsx @@ -0,0 +1,60 @@ +import { Text, View } from "react-native"; +import type { Human } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { cn } from "@/lib/utils"; + +type Size = "xs" | "sm" | "md"; + +interface AvatarProps { + humanId: string | null | undefined; + humans: Human[] | undefined; + size?: Size; + /** True for online presence — adds a green ring (matches desktop). */ + online?: boolean; + /** Background ring used to separate stacked avatars from the chrome. */ + stackBg?: string; + className?: string; +} + +const sizeMap: Record = { + xs: { box: "h-6 w-6", text: "text-[9px]", ring: 1.5 }, + sm: { box: "h-9 w-9", text: "text-xs", ring: 2 }, + md: { box: "h-10 w-10", text: "text-sm", ring: 2 }, +}; + +/** + * Initials avatar with optional online ring (green) and an optional outer + * stack ring used to visually separate overlapping avatars on a busy chrome. + * Matches desktop's avatar + presence pattern (`ring-2 ring-green-500`). + */ +export function Avatar({ + humanId, + humans, + size = "sm", + online = false, + stackBg, + className, +}: AvatarProps) { + const { initials } = resolveHumanDisplay(humanId, humans); + const dims = sizeMap[size]; + + return ( + + + {initials} + + + ); +} diff --git a/js/mobile/src/components/BottomSheet.tsx b/js/mobile/src/components/BottomSheet.tsx new file mode 100644 index 0000000..b6ec381 --- /dev/null +++ b/js/mobile/src/components/BottomSheet.tsx @@ -0,0 +1,174 @@ +import { useEffect, useState } from "react"; +import { + Dimensions, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + View, +} from "react-native"; +import { + initialWindowMetrics, + SafeAreaProvider, + SafeAreaView, +} from "react-native-safe-area-context"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + Easing, + Extrapolation, + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; + +const SCREEN_HEIGHT = Dimensions.get("window").height; +const ANIMATION_MS = 240; + +interface BottomSheetProps { + open: boolean; + onClose: () => void; + /** When true, wrap content in KeyboardAvoidingView so the sheet floats above the keyboard. */ + avoidKeyboard?: boolean; + /** + * Cap on the sheet's height. Defaults to 85%; pass a string like "60%" or + * a number of px when content has a stable footprint. + */ + maxHeight?: number | `${number}%`; + children: React.ReactNode; +} + +/** + * Shared modal sheet shell. Handles slide-in animation, backdrop fade, + * drag-to-dismiss, and modal-safe SafeAreaProvider seeding so iOS modals get + * correct insets on the first frame. The drag handle at the top is rendered + * here too, so callers don't need to draw it themselves. + */ +export function BottomSheet({ + open, + onClose, + avoidKeyboard = false, + maxHeight = "85%", + children, +}: BottomSheetProps) { + // Mount slightly past `open` so the slide-in animation has its starting + // position rendered, and the slide-out animation can play before unmount. + const [mounted, setMounted] = useState(false); + const translateY = useSharedValue(SCREEN_HEIGHT); + + useEffect(() => { + if (open) { + setMounted(true); + requestAnimationFrame(() => { + translateY.value = withSpring(0, { + damping: 24, + stiffness: 260, + mass: 0.7, + }); + }); + } else if (mounted) { + translateY.value = withTiming( + SCREEN_HEIGHT, + { duration: ANIMATION_MS, easing: Easing.in(Easing.cubic) }, + (finished) => { + if (finished) runOnJS(setMounted)(false); + }, + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); + + const sheetPan = Gesture.Pan() + .activeOffsetY(10) + .failOffsetX([-25, 25]) + .onUpdate((e) => { + "worklet"; + translateY.value = Math.max(0, e.translationY); + }) + .onEnd((e) => { + "worklet"; + if (e.translationY > 120 || e.velocityY > 800) { + runOnJS(onClose)(); + } else { + translateY.value = withSpring(0, { + damping: 24, + stiffness: 260, + mass: 0.7, + }); + } + }); + + const sheetStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + + const backdropStyle = useAnimatedStyle(() => { + const opacity = interpolate( + translateY.value, + [0, SCREEN_HEIGHT * 0.7], + [0.55, 0], + Extrapolation.CLAMP, + ); + return { opacity }; + }); + + if (!mounted) return null; + + const Wrapper = avoidKeyboard ? KeyboardAvoidingView : View; + const wrapperProps = avoidKeyboard + ? { behavior: Platform.OS === "ios" ? ("padding" as const) : undefined } + : {}; + + return ( + + + + + + + + + + + + + + + {children} + + + + + + + + ); +} diff --git a/js/mobile/src/components/ComposingIndicator.tsx b/js/mobile/src/components/ComposingIndicator.tsx new file mode 100644 index 0000000..fcc62ea --- /dev/null +++ b/js/mobile/src/components/ComposingIndicator.tsx @@ -0,0 +1,95 @@ +import { useEffect } from "react"; +import { Text, View } from "react-native"; +import Animated, { + Easing, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from "react-native-reanimated"; +import type { Human } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import type { ComposingUser } from "@/features/stream-view/stream-presence-context"; + +interface ComposingIndicatorProps { + users: ComposingUser[]; + networkHumans?: Human[]; +} + +/** + * Slim horizontal pill stack pinned just under the metadata header. Each + * pill is the typing/recording indicator for a single user. Rendered above + * the particle canvas with a translucent background so it reads on any + * media. Mobile equivalent of desktop's vertical writing-mode indicator. + */ +export function ComposingIndicator({ + users, + networkHumans, +}: ComposingIndicatorProps) { + if (users.length === 0) return null; + + return ( + + {users.map((u) => { + const { displayName } = resolveHumanDisplay(u.humanId, networkHumans); + const label = + u.mode === "recording" + ? `${displayName} is recording` + : u.mode === "screen" + ? `${displayName} is sharing` + : `${displayName} is typing`; + + return ( + + + + {label} + + + ); + })} + + ); +} + +function BouncingDots() { + return ( + + + + + + ); +} + +function Dot({ delay }: { delay: number }) { + const y = useSharedValue(0); + + useEffect(() => { + const start = setTimeout(() => { + y.value = withRepeat( + withTiming(-3, { + duration: 360, + easing: Easing.inOut(Easing.quad), + }), + -1, + true, + ); + }, delay); + return () => clearTimeout(start); + }, [delay, y]); + + const style = useAnimatedStyle(() => ({ + transform: [{ translateY: y.value }], + })); + + return ( + + ); +} diff --git a/js/mobile/src/components/RelativeTimestamp.tsx b/js/mobile/src/components/RelativeTimestamp.tsx new file mode 100644 index 0000000..53d60f6 --- /dev/null +++ b/js/mobile/src/components/RelativeTimestamp.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from "react"; +import { Text, type TextProps } from "react-native"; +import { formatDistanceToNow } from "@/lib/time-utils"; + +const MINUTE_MS = 60_000; + +interface RelativeTimestampProps extends Omit { + date: Date; +} + +/** + * Re-renders once per minute so labels like "5m ago" stay accurate without + * any per-card timer wiring at the call site. + */ +export function RelativeTimestamp({ date, ...rest }: RelativeTimestampProps) { + const [, force] = useState(0); + + useEffect(() => { + const interval = setInterval(() => force((n) => n + 1), MINUTE_MS); + return () => clearInterval(interval); + }, []); + + return {formatDistanceToNow(date)}; +} diff --git a/js/mobile/src/config/env.ts b/js/mobile/src/config/env.ts new file mode 100644 index 0000000..b54fe17 --- /dev/null +++ b/js/mobile/src/config/env.ts @@ -0,0 +1,63 @@ +import Constants from "expo-constants"; + +// Expo-side equivalent of desktop's __APP_ENV__ build-time replacement +// (see js/desktop/src/config/env.ts). On mobile we read from app.config.ts +// `extra.appEnv`, which itself reads `process.env.EXPO_PUBLIC_APP_ENV` at +// build time. Defaults to "dev". +// +// Firebase web config is public by design (security is enforced via +// Firestore rules + App Check), so both configs live in source. To refresh, +// run: cd infra/gcp/{dev,prod} && terraform output -json firebase_config + +type FirebaseConfig = { + apiKey: string; + appId: string; + authDomain: string; + messagingSenderId: string; + projectId: string; + storageBucket: string; +}; + +type AppConfig = { + orionUrl: string; + pusherUrl: string; + firebase: FirebaseConfig; + /** Empty string disables Sentry. Same DSN across envs; events are split by `environment` tag. */ + sentryDsn: string; +}; + +const configs: Record<"dev" | "prod", AppConfig> = { + dev: { + orionUrl: "https://orion.dev.flowy.live", + pusherUrl: "wss://pusher.dev.flowy.live/ws", + firebase: { + apiKey: "AIzaSyDF_fbk7tDY9tNxUiOwwi--2nYZWZygMGk", + appId: "1:1006580076785:web:e2a0736d60a78e02b15950", + authDomain: "flowy-dev-440017.firebaseapp.com", + messagingSenderId: "1006580076785", + projectId: "flowy-dev-440017", + storageBucket: "flowy-dev-440017.firebasestorage.app", + }, + sentryDsn: + "https://4bb3af832825929eeec8ab4edd56930f@o4511282312118272.ingest.us.sentry.io/4511282313494528", + }, + prod: { + orionUrl: "https://orion.flowy.live", + pusherUrl: "wss://pusher.flowy.live/ws", + firebase: { + apiKey: "AIzaSyB8it3SKr9DHScHGlpu4uwWicvt8wzjdBg", + appId: "1:68063426854:web:5054f16f50898f5706e9e7", + authDomain: "flowy-prod-440017.firebaseapp.com", + messagingSenderId: "68063426854", + projectId: "flowy-prod-440017", + storageBucket: "flowy-prod-440017.firebasestorage.app", + }, + sentryDsn: + "https://4bb3af832825929eeec8ab4edd56930f@o4511282312118272.ingest.us.sentry.io/4511282313494528", + }, +}; + +const rawEnv = (Constants.expoConfig?.extra as { appEnv?: string } | undefined) + ?.appEnv; +export const appEnv: "dev" | "prod" = rawEnv === "prod" ? "prod" : "dev"; +export const appConfig: AppConfig = configs[appEnv]; diff --git a/js/mobile/src/features/auth/SignInScreen.tsx b/js/mobile/src/features/auth/SignInScreen.tsx new file mode 100644 index 0000000..ab85b2d --- /dev/null +++ b/js/mobile/src/features/auth/SignInScreen.tsx @@ -0,0 +1,193 @@ +import { useState } from "react"; +import { + KeyboardAvoidingView, + Platform, + Pressable, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useAuthStore } from "@/stores/auth-store"; + +type Step = "email" | "code"; + +export function SignInScreen() { + const [step, setStep] = useState("email"); + const [email, setEmail] = useState(""); + + return ( + + + + {step === "email" ? ( + { + setEmail(submittedEmail); + setStep("code"); + }} + /> + ) : ( + setStep("email")} /> + )} + + + + ); +} + +function EmailStep({ onCodeSent }: { onCodeSent: (email: string) => void }) { + const [email, setEmail] = useState(""); + const isRequestingCode = useAuthStore((s) => s.isRequestingCode); + const error = useAuthStore((s) => s.error); + const requestCode = useAuthStore((s) => s.requestCode); + const clearError = useAuthStore((s) => s.clearError); + + const submit = async () => { + try { + await requestCode(email); + onCodeSent(email); + } catch { + // Error surfaced via the store + } + }; + + const disabled = isRequestingCode || email.trim().length === 0; + + return ( + + + Sign in + + Enter your email to receive a sign-in code. + + + + + Email + { + setEmail(text); + if (error) clearError(); + }} + placeholder="you@example.com" + placeholderTextColor="#878787" + keyboardType="email-address" + autoCapitalize="none" + autoCorrect={false} + autoComplete="email" + textContentType="emailAddress" + autoFocus + returnKeyType="go" + onSubmitEditing={submit} + className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base" + /> + + + {error ? ( + {error} + ) : null} + + + + {isRequestingCode ? "Sending..." : "Continue"} + + + + ); +} + +function CodeStep({ email, onBack }: { email: string; onBack: () => void }) { + const [code, setCode] = useState(""); + const isSigningIn = useAuthStore((s) => s.isSigningIn); + const error = useAuthStore((s) => s.error); + const signIn = useAuthStore((s) => s.signIn); + const clearError = useAuthStore((s) => s.clearError); + + const submit = async () => { + try { + await signIn(email, code); + } catch { + // Error surfaced via the store; keep the screen visible + } + }; + + const disabled = isSigningIn || code.trim().length === 0; + + return ( + + + + Check your email + + + We sent a code to{" "} + {email}. + + + + + Code + { + setCode(text); + if (error) clearError(); + }} + placeholder="Enter code" + placeholderTextColor="#878787" + keyboardType="number-pad" + autoFocus + returnKeyType="go" + textContentType="oneTimeCode" + onSubmitEditing={submit} + className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base tracking-widest" + /> + + + {error ? ( + {error} + ) : null} + + + + + {isSigningIn ? "Signing in..." : "Sign in"} + + + + + Back + + + + + ); +} diff --git a/js/mobile/src/features/compose/AudioRecordingOverlay.tsx b/js/mobile/src/features/compose/AudioRecordingOverlay.tsx new file mode 100644 index 0000000..c0bf7db --- /dev/null +++ b/js/mobile/src/features/compose/AudioRecordingOverlay.tsx @@ -0,0 +1,125 @@ +import { useEffect, useRef } from "react"; +import { Pressable, StyleSheet, Text, View } from "react-native"; +import { Mic } from "lucide-react-native"; +import { + RecordingPresets, + setAudioModeAsync, + useAudioRecorder, + useAudioRecorderState, +} from "expo-audio"; +import { logError } from "@/lib/errors"; + +const MAX_DURATION_S = 60; + +interface AudioRecordingOverlayProps { + onComplete: (result: { uri: string; durationMs: number }) => void; + onCancel: () => void; +} + +export function AudioRecordingOverlay({ + onComplete, + onCancel, +}: AudioRecordingOverlayProps) { + const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); + const state = useAudioRecorderState(recorder, 250); + const finalizedRef = useRef(false); + + useEffect(() => { + let active = true; + (async () => { + try { + await setAudioModeAsync({ + allowsRecording: true, + playsInSilentMode: true, + }); + await recorder.prepareToRecordAsync(); + if (!active) return; + recorder.record(); + } catch (err) { + logError(err, { scope: "compose.audio.start" }); + if (active) onCancel(); + } + })(); + + return () => { + active = false; + if (!finalizedRef.current) { + finalizedRef.current = true; + recorder.stop().catch(() => {}); + } + void setAudioModeAsync({ + allowsRecording: false, + playsInSilentMode: true, + }).catch((err) => logError(err, { scope: "compose.audio.exit" })); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const elapsedMs = state.durationMillis ?? 0; + + useEffect(() => { + if (elapsedMs >= MAX_DURATION_S * 1000 && !finalizedRef.current) { + void finish("commit"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [elapsedMs]); + + const finish = async (kind: "commit" | "cancel") => { + if (finalizedRef.current) return; + finalizedRef.current = true; + const durationMs = state.durationMillis ?? 0; + try { + await recorder.stop(); + } catch (err) { + logError(err, { scope: "compose.audio.stop" }); + } + if (kind === "cancel") { + onCancel(); + return; + } + const uri = recorder.uri; + if (!uri) { + onCancel(); + return; + } + onComplete({ uri, durationMs }); + }; + + const elapsedSec = Math.floor(elapsedMs / 1000); + + return ( + + + + + + + + {state.isRecording ? "Recording" : "Starting…"} + + + {elapsedSec.toString().padStart(2, "0")}s · max {MAX_DURATION_S}s + + + + void finish("cancel")} + accessibilityLabel="Cancel recording" + className="rounded-full bg-white/15 px-6 py-3" + > + Cancel + + void finish("commit")} + accessibilityLabel="Stop recording" + className="rounded-full bg-white px-7 py-3" + > + Stop + + + + ); +} diff --git a/js/mobile/src/features/compose/ComposeDock.tsx b/js/mobile/src/features/compose/ComposeDock.tsx new file mode 100644 index 0000000..4b2f80a --- /dev/null +++ b/js/mobile/src/features/compose/ComposeDock.tsx @@ -0,0 +1,298 @@ +import { useCallback, useEffect, useState } from "react"; +import { Pressable, Text, View } from "react-native"; +import { Mic, Type as TypeIcon, Video as VideoIcon } from "lucide-react-native"; +import * as Haptics from "expo-haptics"; +import { + useCameraPermissions, + useMicrophonePermissions, +} from "expo-camera"; +import { toast } from "sonner-native"; +import { cn } from "@/lib/utils"; +import { useEvent } from "@/hooks/use-event"; +import { usePlaybackPauseStore } from "@/stores/playback-pause-store"; +import { useAuthStore } from "@/stores/auth-store"; +import { + createTextParticle, + uploadMediaParticle, +} from "@/lib/upload"; +import type { ParticlePath } from "@/lib/particle-path"; +import { + useStreamComposingBroadcast, + type ComposingMode, +} from "@/features/stream-view/stream-presence-context"; +import { TextComposeModal } from "./TextComposeModal"; +import { VideoRecordingOverlay } from "./VideoRecordingOverlay"; +import { AudioRecordingOverlay } from "./AudioRecordingOverlay"; +import { ReviewSheet } from "./ReviewSheet"; + +type RecordingMode = "video" | "audio"; + +type ComposeUiState = + | { kind: "idle" } + | { kind: "recording"; mode: RecordingMode } + | { + kind: "review"; + mode: RecordingMode; + uri: string; + durationMs: number; + } + | { kind: "uploading" }; + +interface SubmitMediaParams { + fileUri: string; + mimeType: string; + durationMs: number; + source: "camera" | "screen"; +} + +interface ComposeDockProps { + networkId: string; + targetPath: ParticlePath; + silentPresence?: boolean; + submitMedia?: (params: SubmitMediaParams) => Promise; + submitText?: (content: string) => Promise; +} + +export function ComposeDock({ + networkId, + targetPath, + silentPresence = false, + submitMedia, + submitText: submitTextOverride, +}: ComposeDockProps) { + const userId = useAuthStore((s) => s.user?.id); + + const [mode, setMode] = useState("video"); + const [ui, setUi] = useState({ kind: "idle" }); + const [textOpen, setTextOpen] = useState(false); + + const [camPerm, requestCamPerm] = useCameraPermissions(); + const [micPerm, requestMicPerm] = useMicrophonePermissions(); + + // Tell StreamView to fully unmount its expo-video player while we record. + // That player otherwise holds the iOS AVAudioSession and crashes the camera. + const setComposing = usePlaybackPauseStore((s) => s.setComposing); + const isComposing = ui.kind !== "idle" || textOpen; + useEffect(() => { + setComposing(isComposing); + return () => setComposing(false); + }, [isComposing, setComposing]); + + useComposingBroadcast({ ui, textOpen, silent: silentPresence }); + + const ensurePermissions = useCallback( + async (forVideo: boolean): Promise => { + if (forVideo) { + const cam = camPerm?.granted ? camPerm : await requestCamPerm(); + if (!cam.granted) { + toast.error("Camera permission is required to record video."); + return false; + } + } + const mic = micPerm?.granted ? micPerm : await requestMicPerm(); + if (!mic.granted) { + toast.error("Microphone permission is required to record."); + return false; + } + return true; + }, + [camPerm, micPerm, requestCamPerm, requestMicPerm], + ); + + const startRecording = useEvent(async () => { + if (ui.kind !== "idle") return; + const ok = await ensurePermissions(mode === "video"); + if (!ok) return; + void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + setUi({ kind: "recording", mode }); + }); + + const handleRecordingComplete = useCallback( + ({ uri, durationMs }: { uri: string; durationMs: number }) => { + void Haptics.selectionAsync(); + setUi((prev) => { + const m = "mode" in prev ? prev.mode : mode; + return { kind: "review", mode: m, uri, durationMs }; + }); + }, + [mode], + ); + + const handleRecordingCancel = useCallback(() => { + setUi({ kind: "idle" }); + }, []); + + const sendReview = useEvent(async () => { + if (ui.kind !== "review" || !userId) return; + const captured = ui; + setUi({ kind: "uploading" }); + try { + const mimeType = + captured.mode === "audio" ? "audio/mp4" : "video/mp4"; + if (submitMedia) { + await submitMedia({ + fileUri: captured.uri, + mimeType, + durationMs: captured.durationMs, + source: "camera", + }); + } else { + await uploadMediaParticle({ + networkId, + targetPath, + fileUri: captured.uri, + mimeType, + durationMs: captured.durationMs, + source: "camera", + createdByHumanId: userId, + }); + } + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + setUi({ kind: "idle" }); + } catch (err) { + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + setUi(captured); + throw err; + } + }); + + const retake = useCallback(() => setUi({ kind: "idle" }), []); + const cancelReview = useCallback(() => setUi({ kind: "idle" }), []); + + const submitText = useEvent(async (content: string) => { + if (!userId) throw new Error("Not signed in."); + if (submitTextOverride) { + await submitTextOverride(content); + } else { + await createTextParticle({ + networkId, + targetPath, + content, + createdByHumanId: userId, + }); + } + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }); + + const dockHidden = + ui.kind === "review" || + ui.kind === "uploading" || + ui.kind === "recording"; + + return ( + <> + {!dockHidden ? ( + + + + setMode((m) => (m === "video" ? "audio" : "video")) + } + disabled={ui.kind !== "idle"} + accessibilityLabel={`Switch to ${ + mode === "video" ? "audio" : "video" + } mode`} + className={cn( + "h-11 w-11 items-center justify-center rounded-full bg-white/15", + ui.kind !== "idle" && "opacity-40", + )} + > + {mode === "video" ? ( + + ) : ( + + )} + + + + + + + + Tap to record + + + + setTextOpen(true)} + disabled={ui.kind !== "idle"} + accessibilityLabel="Compose text" + className={cn( + "h-11 w-11 items-center justify-center rounded-full bg-white/15", + ui.kind !== "idle" && "opacity-40", + )} + > + + + + + ) : null} + + {ui.kind === "recording" ? ( + ui.mode === "video" ? ( + + ) : ( + + ) + ) : null} + + + + setTextOpen(false)} + onSubmit={submitText} + /> + + ); +} + +function useComposingBroadcast({ + ui, + textOpen, + silent, +}: { + ui: ComposeUiState; + textOpen: boolean; + silent: boolean; +}) { + let broadcast: ReturnType | null; + try { + broadcast = useStreamComposingBroadcast(); + } catch { + broadcast = null; + } + + const mode: ComposingMode | null = + ui.kind === "recording" ? "recording" : textOpen ? "typing" : null; + + useEffect(() => { + if (silent || !broadcast) return; + if (mode) { + broadcast.startComposing(mode); + return () => broadcast?.stopComposing(); + } + }, [mode, silent, broadcast]); +} diff --git a/js/mobile/src/features/compose/ReviewSheet.tsx b/js/mobile/src/features/compose/ReviewSheet.tsx new file mode 100644 index 0000000..47352ea --- /dev/null +++ b/js/mobile/src/features/compose/ReviewSheet.tsx @@ -0,0 +1,154 @@ +import { useEffect, useState } from "react"; +import { Modal, Pressable, Text, View } from "react-native"; +import { + initialWindowMetrics, + SafeAreaProvider, + SafeAreaView, +} from "react-native-safe-area-context"; +import { useVideoPlayer, VideoView } from "expo-video"; +import { Mic } from "lucide-react-native"; +import { toast } from "sonner-native"; +import { cn } from "@/lib/utils"; +import { toUserMessage } from "@/lib/errors"; + +interface ReviewSheetProps { + open: boolean; + /** Local file URI from the recorder. */ + uri: string | null; + mode: "video" | "audio" | null; + durationMs: number; + onSend: () => Promise; + onRetake: () => void; + onCancel: () => void; +} + +/** + * Loop-plays the just-recorded clip and offers Retake / Send. WhatsApp-style + * confirmation: any send-failure surfaces a toast and keeps the sheet open + * so the user doesn't lose their take. + */ +export function ReviewSheet({ + open, + uri, + mode, + durationMs, + onSend, + onRetake, + onCancel, +}: ReviewSheetProps) { + const player = useVideoPlayer(uri ?? "", (p) => { + p.loop = true; + p.muted = false; + p.audioMixingMode = "mixWithOthers"; + }); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (open && uri) { + player.play(); + } + }, [open, uri, player]); + + const handleSend = async () => { + if (submitting) return; + setSubmitting(true); + try { + await onSend(); + } catch (err) { + toast.error(toUserMessage(err)); + setSubmitting(false); + } + }; + + const seconds = Math.max(1, Math.round(durationMs / 1000)); + + return ( + + + + {uri ? ( + mode === "audio" ? ( + + + + + + Voice message · {seconds}s + + + Tap send to share, or retake. + + + + + + ) : ( + + ) + ) : null} + + + + + Cancel + + + + + + + + Retake + + + + + {submitting ? "Sending..." : "Send"} + + + + + + + + ); +} diff --git a/js/mobile/src/features/compose/TextComposeModal.tsx b/js/mobile/src/features/compose/TextComposeModal.tsx new file mode 100644 index 0000000..307a783 --- /dev/null +++ b/js/mobile/src/features/compose/TextComposeModal.tsx @@ -0,0 +1,152 @@ +import { useEffect, useRef, useState } from "react"; +import { + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + Text, + TextInput, + View, +} from "react-native"; +import { + initialWindowMetrics, + SafeAreaProvider, + SafeAreaView, +} from "react-native-safe-area-context"; +import { toast } from "sonner-native"; +import { cn } from "@/lib/utils"; +import { toUserMessage } from "@/lib/errors"; + +const IMMERSIVE_CHAR_LIMIT = 120; + +function getImmersiveStyle(length: number) { + if (length === 0) + return { className: "text-3xl font-semibold leading-snug" }; + if (length < 30) + return { className: "text-5xl font-semibold leading-tight" }; + if (length < 70) + return { className: "text-3xl font-semibold leading-snug" }; + return { className: "text-2xl font-normal leading-snug" }; +} + +interface TextComposeModalProps { + open: boolean; + onClose: () => void; + /** + * Submit handler — must throw if the upload fails so the modal can re-show + * the editor and the user doesn't lose their text. + */ + onSubmit: (content: string) => Promise; +} + +/** + * Immersive full-screen text editor. Mirrors desktop's `text-editor.tsx`: + * dynamic font ramp at 30/70/130 chars, no markdown preview, no gradient. + * Long messages scroll inside the multiline TextInput. Pauses upstream + * playback (the host wraps render in a useSuspendPlayback while open). + */ +export function TextComposeModal({ + open, + onClose, + onSubmit, +}: TextComposeModalProps) { + const [content, setContent] = useState(""); + const [submitting, setSubmitting] = useState(false); + const inputRef = useRef(null); + + // Reset whenever the modal opens fresh. + useEffect(() => { + if (open) { + setContent(""); + setSubmitting(false); + // Re-focus on next tick; iOS occasionally drops the autoFocus call when + // the modal animation is mid-flight. + const t = setTimeout(() => inputRef.current?.focus(), 60); + return () => clearTimeout(t); + } + }, [open]); + + const trimmed = content.trim(); + const canSend = trimmed.length > 0 && !submitting; + + const handleSend = async () => { + if (!canSend) return; + setSubmitting(true); + try { + await onSubmit(trimmed); + onClose(); + } catch (err) { + toast.error(toUserMessage(err)); + setSubmitting(false); + } + }; + + const style = getImmersiveStyle(trimmed.length); + const isImmersive = trimmed.length < IMMERSIVE_CHAR_LIMIT; + + return ( + + + + + + + Cancel + + + + {submitting ? "Sending..." : "Send"} + + + + + + + + + + + + ); +} diff --git a/js/mobile/src/features/compose/VideoRecordingOverlay.tsx b/js/mobile/src/features/compose/VideoRecordingOverlay.tsx new file mode 100644 index 0000000..459e8a4 --- /dev/null +++ b/js/mobile/src/features/compose/VideoRecordingOverlay.tsx @@ -0,0 +1,138 @@ +import { useEffect, useRef, useState } from "react"; +import { Pressable, StyleSheet, Text, View } from "react-native"; +import { CameraView } from "expo-camera"; +import { logError } from "@/lib/errors"; + +const MAX_DURATION_S = 60; + +interface VideoRecordingOverlayProps { + onComplete: (result: { uri: string; durationMs: number }) => void; + onCancel: () => void; +} + +export function VideoRecordingOverlay({ + onComplete, + onCancel, +}: VideoRecordingOverlayProps) { + const cameraRef = useRef(null); + const [cameraReady, setCameraReady] = useState(false); + const [recording, setRecording] = useState(false); + const [elapsedMs, setElapsedMs] = useState(0); + const startedAtRef = useRef(null); + const cancelledRef = useRef(false); + + useEffect(() => { + return () => { + cancelledRef.current = true; + }; + }, []); + + const startRecording = async () => { + const cam = cameraRef.current; + if (!cam || recording || !cameraReady) return; + + setRecording(true); + startedAtRef.current = Date.now(); + + let result: { uri: string } | undefined; + try { + result = await cam.recordAsync({ maxDuration: MAX_DURATION_S }); + } catch (err) { + if (cancelledRef.current) return; + logError(err, { scope: "compose.video.recordAsync" }); + onCancel(); + return; + } + if (cancelledRef.current) return; + + const durationMs = + startedAtRef.current !== null ? Date.now() - startedAtRef.current : 0; + if (result?.uri) { + onComplete({ uri: result.uri, durationMs }); + } else { + onCancel(); + } + }; + + const stopRecording = () => { + cameraRef.current?.stopRecording(); + }; + + const cancel = () => { + cancelledRef.current = true; + if (recording) { + cameraRef.current?.stopRecording(); + } + onCancel(); + }; + + useEffect(() => { + if (!recording) return; + const interval = setInterval(() => { + if (startedAtRef.current === null) return; + setElapsedMs(Date.now() - startedAtRef.current); + }, 250); + return () => clearInterval(interval); + }, [recording]); + + const elapsedSec = Math.floor(elapsedMs / 1000); + + return ( + + setCameraReady(true)} + /> + + {recording ? ( + + + + + REC · {elapsedSec.toString().padStart(2, "0")}s + + + + ) : null} + + + + Cancel + + {recording ? ( + + Stop + + ) : ( + + + + )} + + + ); +} diff --git a/js/mobile/src/features/networks/Drawer.tsx b/js/mobile/src/features/networks/Drawer.tsx new file mode 100644 index 0000000..dee8a6f --- /dev/null +++ b/js/mobile/src/features/networks/Drawer.tsx @@ -0,0 +1,167 @@ +import { useEffect, useRef } from "react"; +import { + Animated, + Dimensions, + Easing, + Modal, + Pressable, + Text, + View, +} from "react-native"; +import { + initialWindowMetrics, + SafeAreaProvider, + SafeAreaView, +} from "react-native-safe-area-context"; +import { useAuthStore } from "@/stores/auth-store"; + +const SCREEN_WIDTH = Dimensions.get("window").width; +const DRAWER_WIDTH = Math.min(320, Math.round(SCREEN_WIDTH * 0.82)); +const ANIM_MS = 220; + +interface DrawerProps { + open: boolean; + onClose: () => void; + onNavigateAccount: () => void; + onNavigateSettings: () => void; +} + +export function Drawer({ + open, + onClose, + onNavigateAccount, +}: DrawerProps) { + const translateX = useRef(new Animated.Value(-DRAWER_WIDTH)).current; + const backdropOpacity = useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.parallel([ + Animated.timing(translateX, { + toValue: open ? 0 : -DRAWER_WIDTH, + duration: ANIM_MS, + easing: Easing.out(Easing.cubic), + useNativeDriver: true, + }), + Animated.timing(backdropOpacity, { + toValue: open ? 0.4 : 0, + duration: ANIM_MS, + easing: Easing.out(Easing.cubic), + useNativeDriver: true, + }), + ]).start(); + }, [open, translateX, backdropOpacity]); + + const user = useAuthStore((s) => s.user); + const signOut = useAuthStore((s) => s.signOut); + const isSigningOut = useAuthStore((s) => s.isSigningOut); + + const initials = user?.email_prefix.slice(0, 2).toUpperCase() ?? "??"; + + return ( + + {/* Modal mounts a separate native view tree on iOS — without a fresh + SafeAreaProvider seeded with initialWindowMetrics, useSafeAreaInsets + inside reports {0,0,0,0} on the first frame and content snaps from + the status bar down to the safe area once metrics resolve. */} + + + + + + + + + + + + {initials} + + + + + {user?.email_prefix ?? ""} + + + {user?.email ?? ""} + + + + + + { + onClose(); + onNavigateAccount(); + }} + /> + + + + { + void signOut(); + }} + tone="destructive" + /> + + + + + + + ); +} + +function DrawerRow({ + label, + onPress, + disabled, + tone = "default", +}: { + label: string; + onPress: () => void; + disabled?: boolean; + tone?: "default" | "destructive"; +}) { + return ( + + + {label} + + + ); +} diff --git a/js/mobile/src/features/networks/NetworkListScreen.tsx b/js/mobile/src/features/networks/NetworkListScreen.tsx new file mode 100644 index 0000000..9a824b6 --- /dev/null +++ b/js/mobile/src/features/networks/NetworkListScreen.tsx @@ -0,0 +1,136 @@ +import { useCallback, useState } from "react"; +import { + ActivityIndicator, + FlatList, + Pressable, + RefreshControl, + Text, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import type { Network } from "@/api/types"; +import { useNetworks } from "@/hooks/use-networks"; +import { useAuthStore } from "@/stores/auth-store"; +import { toUserMessage } from "@/lib/errors"; +import type { RootStackScreenProps } from "@/navigation/types"; +import { Drawer } from "./Drawer"; + +export function NetworkListScreen({ + navigation, +}: RootStackScreenProps<"NetworkList">) { + const [drawerOpen, setDrawerOpen] = useState(false); + const { data, isLoading, refetch, error } = useNetworks(); + const user = useAuthStore((s) => s.user); + const initials = user?.email_prefix.slice(0, 2).toUpperCase() ?? "??"; + + // Local refreshing state — driving RefreshControl from react-query's + // isRefetching can leave the native spinner visually stuck after the + // screen is detached/reattached by native-stack. + const [refreshing, setRefreshing] = useState(false); + const onRefresh = useCallback(async () => { + setRefreshing(true); + try { + await refetch(); + } finally { + setRefreshing(false); + } + }, [refetch]); + + return ( + + + setDrawerOpen(true)} + accessibilityLabel="Open menu" + className="bg-muted h-9 w-9 items-center justify-center rounded-full" + > + + {initials} + + + Flowy + + + + {isLoading ? ( + + + + ) : error ? ( + + + {toUserMessage(error)} + + refetch()} className="mt-3 px-4 py-2"> + Retry + + + ) : !data || data.length === 0 ? ( + + ) : ( + item.id} + contentContainerClassName="p-4 gap-2" + refreshControl={ + + } + renderItem={({ item }) => ( + + navigation.navigate("StreamList", { networkId: item.id }) + } + /> + )} + /> + )} + + setDrawerOpen(false)} + onNavigateAccount={() => navigation.navigate("Account")} + onNavigateSettings={() => navigation.navigate("Settings")} + /> + + ); +} + +function NetworkCard({ + network, + onPress, +}: { + network: Network; + onPress: () => void; +}) { + return ( + + + + {network.name} + + + {network.humans.length}{" "} + {network.humans.length === 1 ? "member" : "members"} + + + + + ); +} + +function EmptyState() { + return ( + + + You aren't in any networks yet. + + + Ask a friend for an invite, or create one on desktop. + + + ); +} diff --git a/js/mobile/src/features/settings/AccountScreen.tsx b/js/mobile/src/features/settings/AccountScreen.tsx new file mode 100644 index 0000000..ee1a133 --- /dev/null +++ b/js/mobile/src/features/settings/AccountScreen.tsx @@ -0,0 +1,37 @@ +import { Pressable, Text, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useAuthStore } from "@/stores/auth-store"; +import type { RootStackScreenProps } from "@/navigation/types"; + +export function AccountScreen({ navigation }: RootStackScreenProps<"Account">) { + const user = useAuthStore((s) => s.user); + + return ( + + + navigation.goBack()} className="px-2 py-1"> + + + + Account + + + + + + + + + ); +} + +function Field({ label, value }: { label: string; value: string }) { + return ( + + + {label} + + {value} + + ); +} diff --git a/js/mobile/src/features/settings/SettingsScreen.tsx b/js/mobile/src/features/settings/SettingsScreen.tsx new file mode 100644 index 0000000..1a4b4e7 --- /dev/null +++ b/js/mobile/src/features/settings/SettingsScreen.tsx @@ -0,0 +1,27 @@ +import { Pressable, Text, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import type { RootStackScreenProps } from "@/navigation/types"; + +export function SettingsScreen({ + navigation, +}: RootStackScreenProps<"Settings">) { + return ( + + + navigation.goBack()} className="px-2 py-1"> + + + + Settings + + + + + + + Theme, notifications, and account preferences land here later. + + + + ); +} diff --git a/js/mobile/src/features/stream-view/DeletedParticleView.tsx b/js/mobile/src/features/stream-view/DeletedParticleView.tsx new file mode 100644 index 0000000..f90c750 --- /dev/null +++ b/js/mobile/src/features/stream-view/DeletedParticleView.tsx @@ -0,0 +1,52 @@ +import { useEffect } from "react"; +import { Text, View } from "react-native"; +import { Trash2 } from "lucide-react-native"; +import type { Particle } from "@/api/types"; +import { useNetwork } from "@/hooks/use-networks"; +import { resolveHumanDisplay } from "@/lib/humans"; + +// How long to linger on a tombstone before auto-advancing. Same cadence as +// desktop — a beat long enough to read "this was deleted," not so long it +// stalls the stream. +const TOMBSTONE_DURATION_MS = 2000; + +interface DeletedParticleViewProps { + particle: Particle; + networkId: string; + paused: boolean; + onEnded: () => void; +} + +export function DeletedParticleView({ + particle, + networkId, + paused, + onEnded, +}: DeletedParticleViewProps) { + const network = useNetwork(networkId); + const deleterId = + "deleted_by_human_id" in particle ? particle.deleted_by_human_id : undefined; + const deleter = deleterId + ? resolveHumanDisplay(deleterId, network?.humans) + : null; + + useEffect(() => { + if (paused) return; + const timeout = setTimeout(onEnded, TOMBSTONE_DURATION_MS); + return () => clearTimeout(timeout); + }, [paused, onEnded, particle.id]); + + return ( + + + + This particle was deleted + + {deleter ? ( + + by {deleter.displayName} + + ) : null} + + ); +} diff --git a/js/mobile/src/features/stream-view/FallbackParticleView.tsx b/js/mobile/src/features/stream-view/FallbackParticleView.tsx new file mode 100644 index 0000000..3a25ffc --- /dev/null +++ b/js/mobile/src/features/stream-view/FallbackParticleView.tsx @@ -0,0 +1,89 @@ +import { useEffect } from "react"; +import { Text, View } from "react-native"; +import { + FileIcon, + HelpCircle, + ScrollText, + BookOpen, + type LucideIcon, +} from "lucide-react-native"; +import type { Particle } from "@/api/types"; +import { useNetwork } from "@/hooks/use-networks"; +import { resolveHumanDisplay } from "@/lib/humans"; + +const TYPE_META: Record = { + quest: { icon: ScrollText, label: "Quest" }, + paper: { icon: BookOpen, label: "Paper" }, + file: { icon: FileIcon, label: "File" }, +}; + +const PLACEHOLDER_DURATION_MS = 5000; + +interface FallbackParticleViewProps { + particle: Particle; + networkId: string; + paused: boolean; + onEnded: () => void; +} + +export function FallbackParticleView({ + particle, + networkId, + paused, + onEnded, +}: FallbackParticleViewProps) { + const network = useNetwork(networkId); + const creator = resolveHumanDisplay( + particle.created_by_human_id, + network?.humans, + ); + const meta = TYPE_META[particle.type] ?? { + icon: HelpCircle, + label: particle.type, + }; + const Icon = meta.icon; + const title = (() => { + switch (particle.type) { + case "quest": + return particle.properties.title; + case "paper": + return particle.properties.title; + case "file": + return particle.properties.filename; + case "folder": + return particle.properties.name; + default: + return null; + } + })(); + + useEffect(() => { + if (paused) return; + const timeout = setTimeout(onEnded, PLACEHOLDER_DURATION_MS); + return () => clearTimeout(timeout); + }, [paused, onEnded, particle.id]); + + return ( + + + + + + + {meta.label} + + {title ? ( + + {title} + + ) : null} + + + + From {creator.displayName} + + View on desktop + + + ); +} diff --git a/js/mobile/src/features/stream-view/MediaParticleView.tsx b/js/mobile/src/features/stream-view/MediaParticleView.tsx new file mode 100644 index 0000000..9e8aeb8 --- /dev/null +++ b/js/mobile/src/features/stream-view/MediaParticleView.tsx @@ -0,0 +1,273 @@ +import { useEffect, useRef, useState } from "react"; +import { ActivityIndicator, Text, View } from "react-native"; +import { Mic, Video as VideoIcon } from "lucide-react-native"; +import { useEventListener } from "expo"; +import { useVideoPlayer, VideoView, type VideoPlayerStatus } from "expo-video"; +import type { Particle } from "@/api/types"; +import { apiClient } from "@/api/client"; +import { logError } from "@/lib/errors"; +import { useEvent } from "@/hooks/use-event"; + +type MediaParticle = Extract; + +interface MediaParticleViewProps { + particle: MediaParticle; + paused: boolean; + onEnded: () => void; + onProgress: (ratio: number) => void; + /** "cover" fills the screen (may crop); "contain" fits the whole frame. */ + contentFit?: "cover" | "contain"; +} + +const TICK_MS = 150; + +/** + * Plays MP4 / MOV / m4a content via expo-video. Desktop currently records + * WebM, which AVPlayer can't decode; the particle processor worker produces + * an iOS-playable MP4/m4a variant and writes `transcoded_object_id` / + * `transcoded_mime_type` to the particle. While that work is in flight, we + * show a placeholder and let the Firestore listener + * swap us into the playable state once the worker finishes. + * + * The signed download URL is fetched lazily via apiClient.getParticleDownloadUrl + * (Orion-issued, time-limited). We show a spinner while that resolves, then + * mount the player and report progress via a 150ms tick reading the player's + * currentTime — same model as desktop's MediaParticleView. + */ +export function MediaParticleView({ + particle, + paused, + onEnded, + onProgress, + contentFit = "cover", +}: MediaParticleViewProps) { + const activeObjectId = + particle.properties.transcoded_object_id ?? particle.properties.object_id; + const activeMime = + particle.properties.transcoded_mime_type ?? particle.properties.mime_type; + const isAudio = activeMime.startsWith("audio/"); + const isPlayable = isPlayableMime(activeMime); + + // Reset progress as the active particle changes — independent of playback + // state — so the segmented bar drops back to 0 immediately. + useEffect(() => { + onProgress(0); + }, [particle.id, onProgress]); + + if (!isPlayable) { + return ; + } + + return ( + + ); +} + +function PlayableMediaView({ + particle, + activeObjectId, + isAudio, + paused, + onEnded, + onProgress, + contentFit, +}: { + particle: MediaParticle; + activeObjectId: string; + isAudio: boolean; + paused: boolean; + onEnded: () => void; + onProgress: (ratio: number) => void; + contentFit: "cover" | "contain"; +}) { + const [sourceUri, setSourceUri] = useState(null); + const [resolveError, setResolveError] = useState(null); + + // Fetch the signed download URL once per active object. Orion URLs are + // time-limited — we treat the URL as one-shot for this view's lifetime. + // Re-runs when the worker writes `transcoded_object_id` and the parent + // resolves a new active object id. + useEffect(() => { + let cancelled = false; + setSourceUri(null); + setResolveError(null); + apiClient + .getParticleDownloadUrl(activeObjectId) + .then((url) => { + if (!cancelled) setSourceUri(url); + }) + .catch((err) => { + logError(err, { scope: "media.download-url" }); + if (!cancelled) setResolveError(err as Error); + }); + return () => { + cancelled = true; + }; + }, [activeObjectId, particle.id]); + + const player = useVideoPlayer(sourceUri ?? "", (p) => { + p.loop = false; + p.muted = false; + p.timeUpdateEventInterval = 0.15; + // Don't take exclusive ownership of the iOS AVAudioSession. Without this + // the player blocks expo-camera from acquiring the session for video + // recording (audio works because expo-audio deactivates other sessions + // natively before claiming the session). + p.audioMixingMode = "mixWithOthers"; + }); + + // Drive play/pause from the suspender store. The player itself is forgiving + // about extra play/pause calls so we don't gate this. + useEffect(() => { + if (!sourceUri) return; + if (paused) { + player.pause(); + } else { + player.play(); + } + }, [paused, sourceUri, player]); + + // End-of-clip → advance. We listen to status flips rather than computing + // duration ratios because video duration may be 0 for the first frame or two. + useEventListener(player, "statusChange", ({ status }) => { + if (status === ("idle" satisfies VideoPlayerStatus)) { + // ignored — happens during source swap + } + }); + + const onEndedStable = useEvent(onEnded); + const onProgressStable = useEvent(onProgress); + + // Progress tick: report currentTime / duration each TICK_MS. Bail when the + // player isn't ready yet (duration = 0). + useEffect(() => { + if (!sourceUri || paused) return; + const interval = setInterval(() => { + const duration = player.duration; + if (!duration || duration <= 0) return; + const ratio = Math.min(player.currentTime / duration, 1); + onProgressStable(ratio); + if (ratio >= 0.999) { + clearInterval(interval); + onEndedStable(); + } + }, TICK_MS); + return () => clearInterval(interval); + }, [sourceUri, paused, player, onEndedStable, onProgressStable]); + + if (resolveError) { + return ( + + + Couldn't load this {isAudio ? "voice message" : "video"}. + + + Tap forward to continue. + + + ); + } + + if (!sourceUri) { + return ( + + + + ); + } + + // Audio-only: hide the (blank) video surface and show a static face. The + // VideoView still renders 0×0 so the audio track keeps playing. + if (isAudio) { + return ( + + + + + + + + + Voice message + + + ); + } + + // Video: full-bleed. Default cover so portrait mobile captures fill the + // screen; the user can flip to contain via the top-right toggle when desktop + // captures at odd aspect ratios get cropped uncomfortably. + return ( + + + + ); +} + +// Shown while the particle processor worker is producing the iOS-playable +// MP4/m4a variant. The Firestore listener will re-render this view once +// `transcoded_object_id` lands on the particle, which swaps us into +// PlayableMediaView. We deliberately do not auto-advance — the user is here +// to consume this content; if the worker is slow they can tap forward. +function ProcessingForMobilePlaceholder({ isAudio }: { isAudio: boolean }) { + return ( + + + {isAudio ? ( + + ) : ( + + )} + + + {isAudio ? "Voice message" : "Video message"} + + + + View on desktop + + + + Please view this on desktop only. + + + ); +} + +function isPlayableMime(mime: string): boolean { + // expo-video uses AVPlayer on iOS — reliable for h264 in mp4 / mov / m4a. + // WebM/VP9 (the legacy desktop format) is not decodable. + return ( + mime === "video/mp4" || + mime === "video/quicktime" || + mime === "audio/mp4" || + mime === "audio/aac" || + mime === "audio/x-m4a" || + mime === "audio/mpeg" + ); +} diff --git a/js/mobile/src/features/stream-view/PlaybackPageIndicator.tsx b/js/mobile/src/features/stream-view/PlaybackPageIndicator.tsx new file mode 100644 index 0000000..70caa4f --- /dev/null +++ b/js/mobile/src/features/stream-view/PlaybackPageIndicator.tsx @@ -0,0 +1,101 @@ +import { useEffect } from "react"; +import { View } from "react-native"; +import Animated, { + Easing, + cancelAnimation, + useAnimatedStyle, + useSharedValue, + withTiming, +} from "react-native-reanimated"; + +interface PlaybackPageIndicatorProps { + total: number; + current: number; + /** 0–1 progress for the active segment. Source ticks at ~100ms. */ + progress: number; + paused: boolean; +} + +const SEGMENT_GAP = 3; +const SEGMENT_HEIGHT = 2.5; +const SMOOTHING_MS = 300; + +/** + * Snapchat-style segmented progress bar. Past segments full, future empty, + * active segment animated. The 300ms linear smoothing absorbs the 100ms + * tick from the particle view source so motion looks continuous at 60fps. + */ +export function PlaybackPageIndicator({ + total, + current, + progress, + paused, +}: PlaybackPageIndicatorProps) { + if (total === 0) return null; + + return ( + + {Array.from({ length: total }).map((_, i) => ( + + ))} + + ); +} + +interface SegmentProps { + isActive: boolean; + isPast: boolean; + progress: number; + paused: boolean; +} + +function Segment({ isActive, isPast, progress, paused }: SegmentProps) { + // Each segment owns its own width animation. Past = 1, future = 0, + // active = animated toward `progress`. Reanimated keeps the tween on the + // UI thread so JS thread stalls (e.g. the 100ms text tick re-render) + // can't drop frames here. + const fill = useSharedValue(isPast ? 1 : 0); + + useEffect(() => { + if (isPast) { + cancelAnimation(fill); + fill.value = withTiming(1, { duration: 120, easing: Easing.out(Easing.cubic) }); + return; + } + if (!isActive) { + cancelAnimation(fill); + fill.value = 0; + return; + } + if (paused) { + cancelAnimation(fill); + return; + } + fill.value = withTiming(progress, { + duration: SMOOTHING_MS, + easing: Easing.linear, + }); + }, [isPast, isActive, progress, paused, fill]); + + const fillStyle = useAnimatedStyle(() => ({ + width: `${Math.min(Math.max(fill.value, 0), 1) * 100}%`, + })); + + return ( + + + + ); +} diff --git a/js/mobile/src/features/stream-view/ReactionSheet.tsx b/js/mobile/src/features/stream-view/ReactionSheet.tsx new file mode 100644 index 0000000..609f29e --- /dev/null +++ b/js/mobile/src/features/stream-view/ReactionSheet.tsx @@ -0,0 +1,367 @@ +import { useEffect, useMemo, useState } from "react"; +import { + Dimensions, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + Text, + TextInput, + View, +} from "react-native"; +import { + initialWindowMetrics, + SafeAreaProvider, + SafeAreaView, +} from "react-native-safe-area-context"; +import { Send, X } from "lucide-react-native"; +import * as Haptics from "expo-haptics"; +import { + Gesture, + GestureDetector, +} from "react-native-gesture-handler"; +import Animated, { + Easing, + Extrapolation, + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; +import { REACTION_EMOJIS, type Reactions } from "@/api/types"; +import type { Human } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { cn } from "@/lib/utils"; +import { useSuspendPlayback } from "@/hooks/use-suspend-playback"; + +const TEXT_REACTION_MAX = 40; +const SCREEN_HEIGHT = Dimensions.get("window").height; +const ANIMATION_MS = 240; + +const EMOJI_SET = new Set(REACTION_EMOJIS); + +interface ReactionSheetProps { + open: boolean; + onClose: () => void; + reactions: Reactions; + currentHumanId: string; + humans: Human[] | undefined; + /** + * Toggle a reaction (emoji or text). Adds if the current human hasn't + * reacted, removes if they have. Mirrors desktop's `onToggle` exactly. + */ + onToggle: (key: string) => void; +} + +/** + * Slide-up reaction sheet — the mobile replacement for desktop's right-edge + * reaction stack. Tap an emoji to toggle, or send a custom text reaction + * (40-char cap). Existing reactions appear as toggleable pills at the top. + * + * Playback is suspended via `useSuspendPlayback` while the sheet is open so + * the active particle doesn't auto-advance under the user. Drag the sheet + * down past 30% of its travel to dismiss; everything else springs back. + */ +export function ReactionSheet({ + open, + onClose, + reactions, + currentHumanId, + humans, + onToggle, +}: ReactionSheetProps) { + // Suspend playback whenever the sheet is mounted-and-open. The Modal + // controls visibility so we tie the suspender to `open` directly. + useSuspendPlayback(open, "reactions-sheet"); + + // We mount the modal slightly delayed from `open` so the slide-up animation + // has its starting position rendered. Using local `mounted` state lets us + // play the close animation before unmounting. + const [mounted, setMounted] = useState(false); + const translateY = useSharedValue(SCREEN_HEIGHT); + + useEffect(() => { + if (open) { + setMounted(true); + // Schedule animation after the modal mounts + requestAnimationFrame(() => { + translateY.value = withSpring(0, { + damping: 24, + stiffness: 260, + mass: 0.7, + }); + }); + } else if (mounted) { + translateY.value = withTiming( + SCREEN_HEIGHT, + { duration: ANIMATION_MS, easing: Easing.in(Easing.cubic) }, + (finished) => { + if (finished) runOnJS(setMounted)(false); + }, + ); + } + // intentional: only react to `open`. Closing animation reads from `mounted`. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); + + const dismiss = () => { + onClose(); + }; + + const sheetPan = Gesture.Pan() + .activeOffsetY(10) + .failOffsetX([-25, 25]) + .onUpdate((e) => { + "worklet"; + translateY.value = Math.max(0, e.translationY); + }) + .onEnd((e) => { + "worklet"; + if (e.translationY > 120 || e.velocityY > 800) { + runOnJS(dismiss)(); + } else { + translateY.value = withSpring(0, { + damping: 24, + stiffness: 260, + mass: 0.7, + }); + } + }); + + const sheetStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + + const backdropStyle = useAnimatedStyle(() => { + const opacity = interpolate( + translateY.value, + [0, SCREEN_HEIGHT * 0.7], + [0.55, 0], + Extrapolation.CLAMP, + ); + return { opacity }; + }); + + // --- Existing reaction pills --- + const activeEmojis = REACTION_EMOJIS.filter( + (e) => reactions?.[e] && (reactions[e]?.length ?? 0) > 0, + ); + const activeTextKeys = useMemo( + () => + Object.keys(reactions ?? {}).filter( + (k) => + !EMOJI_SET.has(k) && (reactions?.[k]?.length ?? 0) > 0, + ), + [reactions], + ); + + // --- Text reaction input --- + const [text, setText] = useState(""); + + useEffect(() => { + if (open) setText(""); + }, [open]); + + const submitText = () => { + const trimmed = text.trim(); + if (!trimmed) return; + void Haptics.selectionAsync(); + onToggle(trimmed.slice(0, TEXT_REACTION_MAX)); + setText(""); + onClose(); + }; + + const handleEmoji = (emoji: string) => { + void Haptics.selectionAsync(); + onToggle(emoji); + onClose(); + }; + + if (!mounted) return null; + + return ( + + + + + + + + + + + + + {/* Drag handle — affords downward dismissal at a glance. */} + + + + + React + + + + + + + + {/* Existing reactions row — tap a pill to toggle yours. */} + {activeEmojis.length > 0 || activeTextKeys.length > 0 ? ( + + {activeEmojis.map((emoji) => { + const reactors = reactions![emoji]; + const isMine = reactors.includes(currentHumanId); + return ( + handleEmoji(emoji)} + className={cn( + "flex-row items-center gap-1.5 rounded-full px-3 py-1.5", + isMine + ? "bg-white/25 border border-white/40" + : "bg-white/10", + )} + > + {emoji} + + {reactors.length} + + + ); + })} + + {activeTextKeys.map((key) => { + const reactors = reactions![key]; + const isMine = reactors.includes(currentHumanId); + const firstReactor = resolveHumanDisplay( + reactors[0], + humans, + ); + return ( + handleEmoji(key)} + className={cn( + "flex-row items-center gap-1.5 rounded-full px-2.5 py-1.5 max-w-[220px]", + isMine + ? "bg-white/25 border border-white/40" + : "bg-white/10", + )} + > + + + {firstReactor.initials} + + + + {key} + + {reactors.length > 1 ? ( + + {reactors.length} + + ) : null} + + ); + })} + + ) : null} + + {/* Quick-pick emoji palette — six big tappable buttons. */} + + {REACTION_EMOJIS.slice(0, 6).map((emoji) => { + const isMine = + reactions?.[emoji]?.includes(currentHumanId) ?? false; + return ( + handleEmoji(emoji)} + accessibilityLabel={`React with ${emoji}`} + className={cn( + "h-14 w-14 items-center justify-center rounded-full", + isMine ? "bg-white/25" : "bg-white/10", + )} + > + {emoji} + + ); + })} + + + {/* Text reaction input — 40-char cap matches desktop. */} + + + setText(v.slice(0, TEXT_REACTION_MAX))} + placeholder="Send a quick reply..." + placeholderTextColor="rgba(255,255,255,0.4)" + maxLength={TEXT_REACTION_MAX} + autoCapitalize="none" + autoCorrect={false} + onSubmitEditing={submitText} + returnKeyType="send" + className="text-white text-base" + /> + + + + + + + + + + + + + ); +} diff --git a/js/mobile/src/features/stream-view/ReactionStack.tsx b/js/mobile/src/features/stream-view/ReactionStack.tsx new file mode 100644 index 0000000..3b64714 --- /dev/null +++ b/js/mobile/src/features/stream-view/ReactionStack.tsx @@ -0,0 +1,124 @@ +import { useMemo } from "react"; +import { Pressable, Text, View } from "react-native"; +import { Plus } from "lucide-react-native"; +import * as Haptics from "expo-haptics"; +import { REACTION_EMOJIS, type Reactions } from "@/api/types"; +import type { Human } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { cn } from "@/lib/utils"; + +const EMOJI_SET = new Set(REACTION_EMOJIS); + +interface ReactionStackProps { + reactions: Reactions; + currentHumanId: string; + humans: Human[] | undefined; + /** Toggle a reaction (emoji or text) — same contract as ReactionSheet's onToggle. */ + onToggle: (key: string) => void; + /** Open the full reaction sheet for emoji + custom-text picking. */ + onOpenSheet: () => void; +} + +/** + * Right-edge reaction stack — mobile counterpart of desktop's ReactionBar. + * Sits vertically centered on the right side of the canvas so the user can + * see existing reactions at a glance and tap to toggle their own. The "+" + * affordance opens the ReactionSheet for the full picker (emoji or text). + */ +export function ReactionStack({ + reactions, + currentHumanId, + humans, + onToggle, + onOpenSheet, +}: ReactionStackProps) { + const activeEmojis = REACTION_EMOJIS.filter( + (emoji) => reactions?.[emoji] && (reactions[emoji]?.length ?? 0) > 0, + ); + const activeTextKeys = useMemo( + () => + Object.keys(reactions ?? {}).filter( + (k) => !EMOJI_SET.has(k) && (reactions?.[k]?.length ?? 0) > 0, + ), + [reactions], + ); + + const handleToggle = (key: string) => { + void Haptics.selectionAsync(); + onToggle(key); + }; + + return ( + + {activeEmojis.map((emoji) => { + const reactors = reactions?.[emoji] ?? []; + const isMine = reactors.includes(currentHumanId); + return ( + handleToggle(emoji)} + className={cn( + "flex-row items-center gap-1 rounded-full px-2 py-1", + isMine ? "bg-white/25" : "bg-black/45", + )} + style={ + isMine + ? { borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" } + : undefined + } + > + {emoji} + + {reactors.length} + + + ); + })} + + {activeTextKeys.map((text) => { + const reactors = reactions?.[text] ?? []; + const isMine = reactors.includes(currentHumanId); + const firstReactor = resolveHumanDisplay(reactors[0], humans); + return ( + handleToggle(text)} + className={cn( + "flex-row items-center gap-1.5 rounded-full py-1 pl-1 pr-2.5", + isMine ? "bg-white/25" : "bg-black/45", + )} + style={[ + { maxWidth: 200 }, + isMine + ? { borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" } + : null, + ]} + > + + + {firstReactor.initials} + + + + {text} + + {reactors.length > 1 ? ( + {reactors.length} + ) : null} + + ); + })} + + + + + + ); +} diff --git a/js/mobile/src/features/stream-view/RenameStreamSheet.tsx b/js/mobile/src/features/stream-view/RenameStreamSheet.tsx new file mode 100644 index 0000000..6a94afb --- /dev/null +++ b/js/mobile/src/features/stream-view/RenameStreamSheet.tsx @@ -0,0 +1,89 @@ +import { useEffect, useState } from "react"; +import { Pressable, Text, TextInput, View } from "react-native"; +import { toast } from "sonner-native"; +import { cn } from "@/lib/utils"; +import { toUserMessage } from "@/lib/errors"; +import { updateParticleProperties } from "@/lib/firestore-particles"; +import { particlePath, toFirestoreDocPath } from "@/lib/particle-path"; +import { useSuspendPlayback } from "@/hooks/use-suspend-playback"; +import { BottomSheet } from "@/components/BottomSheet"; + +interface RenameStreamSheetProps { + open: boolean; + onClose: () => void; + networkId: string; + streamId: string; + currentName: string; +} + +export function RenameStreamSheet({ + open, + onClose, + networkId, + streamId, + currentName, +}: RenameStreamSheetProps) { + useSuspendPlayback(open, "rename-stream"); + + const [name, setName] = useState(currentName); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (open) { + setName(currentName); + setSaving(false); + } + }, [open, currentName]); + + const trimmed = name.trim(); + const canSave = !saving && trimmed.length > 0 && trimmed !== currentName; + + const handleSave = async () => { + if (!canSave) return; + setSaving(true); + try { + const docPath = toFirestoreDocPath(particlePath(networkId, [streamId])); + await updateParticleProperties<"stream">(docPath, { name: trimmed }); + onClose(); + } catch (err) { + toast.error(toUserMessage(err)); + setSaving(false); + } + }; + + return ( + + + + Cancel + + Rename + + + {saving ? "Saving..." : "Save"} + + + + + + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamActionsSheet.tsx b/js/mobile/src/features/stream-view/StreamActionsSheet.tsx new file mode 100644 index 0000000..84d083a --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamActionsSheet.tsx @@ -0,0 +1,118 @@ +import { Pressable, Text, View } from "react-native"; +import { + CircleCheckBig, + CircleDot, + Pencil, + Trash2, + Users, +} from "lucide-react-native"; +import { cn } from "@/lib/utils"; +import { BottomSheet } from "@/components/BottomSheet"; + +export type StreamActionId = + | "toggle-status" + | "rename" + | "members" + | "delete-particle"; + +interface StreamActionsSheetProps { + open: boolean; + onClose: () => void; + onSelect: (action: StreamActionId) => void; + streamStatus: "open" | "closed"; + isCreator: boolean; + /** True when the *current* particle is one this user can soft-delete. */ + canDeleteParticle: boolean; +} + +export function StreamActionsSheet({ + open, + onClose, + onSelect, + streamStatus, + isCreator, + canDeleteParticle, +}: StreamActionsSheetProps) { + const choose = (id: StreamActionId) => { + onClose(); + onSelect(id); + }; + + return ( + + + + ) : ( + + ) + } + label={ + streamStatus === "open" ? "Close stream" : "Reopen stream" + } + onPress={() => choose("toggle-status")} + /> + } + label="Members" + onPress={() => choose("members")} + /> + {isCreator ? ( + } + label="Rename stream" + onPress={() => choose("rename")} + /> + ) : null} + {canDeleteParticle ? ( + } + label="Delete particle" + tone="destructive" + onPress={() => choose("delete-particle")} + /> + ) : null} + + + + + Cancel + + + + ); +} + +function ActionRow({ + icon, + label, + onPress, + tone = "default", +}: { + icon: React.ReactNode; + label: string; + onPress: () => void; + tone?: "default" | "destructive"; +}) { + return ( + + {icon} + + {label} + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamMembersSheet.tsx b/js/mobile/src/features/stream-view/StreamMembersSheet.tsx new file mode 100644 index 0000000..bcbeeae --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamMembersSheet.tsx @@ -0,0 +1,273 @@ +import { useMemo } from "react"; +import { Pressable, ScrollView, Text, View } from "react-native"; +import { Globe, Lock, X } from "lucide-react-native"; +import { toast } from "sonner-native"; +import type { Particle } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { useNetwork } from "@/hooks/use-networks"; +import { particlePath, toFirestoreDocPath } from "@/lib/particle-path"; +import { updateParticleVisibleTo } from "@/lib/firestore-particles"; +import { + buildCustomVisibility, + buildNetworkVisibility, + parseVisibleTo, +} from "@/lib/stream-visibility"; +import { toUserMessage } from "@/lib/errors"; +import { useSuspendPlayback } from "@/hooks/use-suspend-playback"; +import { BottomSheet } from "@/components/BottomSheet"; +import { Avatar } from "@/components/Avatar"; +import { useStreamPresence } from "./stream-presence-context"; + +interface StreamMembersSheetProps { + open: boolean; + onClose: () => void; + networkId: string; + streamParticle: Particle & { type: "stream" }; + isCreator: boolean; +} + +/** + * Read-only-for-non-creators view of who can see the stream, plus an inline + * editor for creators to flip between network-wide and per-person and to + * add/remove people. Mobile counterpart of stream-members-overlay.tsx. + */ +export function StreamMembersSheet({ + open, + onClose, + networkId, + streamParticle, + isCreator, +}: StreamMembersSheetProps) { + useSuspendPlayback(open, "stream-members"); + + const { onlineHumanIds } = useStreamPresence(); + const network = useNetwork(networkId); + const humans = network?.humans ?? []; + const creatorId = streamParticle.created_by_human_id; + const visibility = parseVisibleTo(streamParticle.visible_to, networkId); + + const docPath = useMemo( + () => toFirestoreDocPath(particlePath(networkId, [streamParticle.id])), + [networkId, streamParticle.id], + ); + + const memberIds = + visibility.mode === "network" + ? humans.map((h) => h.id) + : visibility.humanIds; + const memberSet = new Set(memberIds); + const availableToAdd = humans.filter((h) => !memberSet.has(h.id)); + + const apply = async (next: string[]) => { + try { + await updateParticleVisibleTo(docPath, next); + } catch (err) { + toast.error(toUserMessage(err)); + } + }; + + const setNetworkWide = () => apply(buildNetworkVisibility(networkId)); + const setCustomOnlyCreator = () => apply(buildCustomVisibility([creatorId])); + + const removeMember = (id: string) => { + if (visibility.mode !== "custom") return; + if (id === creatorId) return; + const next = visibility.humanIds.filter((x) => x !== id); + if (next.length === 0) return; + void apply(buildCustomVisibility(next)); + }; + + const addMember = (id: string) => { + if (visibility.mode !== "custom") return; + void apply(buildCustomVisibility([...visibility.humanIds, id])); + }; + + return ( + + + + Members + + + + + + + + Visibility + + {isCreator ? ( + + } + label="Network-wide" + onPress={setNetworkWide} + /> + } + label="Specific people" + onPress={setCustomOnlyCreator} + /> + + ) : ( + + {visibility.mode === "network" ? ( + <> + + + Everyone in {network?.name ?? "network"} + + + ) : ( + <> + + + {memberIds.length} specific{" "} + {memberIds.length === 1 ? "person" : "people"} + + + )} + + )} + + + + + + {visibility.mode === "network" ? "Has access" : "People"} ·{" "} + {memberIds.length} + + {memberIds.map((id) => { + const display = resolveHumanDisplay(id, humans); + const isCreatorRow = id === creatorId; + const canRemove = + isCreator && visibility.mode === "custom" && !isCreatorRow; + return ( + + + + + {display.displayName} + + {display.exists ? ( + + {display.email} + + ) : null} + + {isCreatorRow ? ( + + Creator + + ) : canRemove ? ( + removeMember(id)} + hitSlop={10} + accessibilityLabel={`Remove ${display.displayName}`} + > + + + ) : null} + + ); + })} + + + {isCreator && + visibility.mode === "custom" && + availableToAdd.length > 0 ? ( + + + Add people + + {availableToAdd.map((human) => { + const display = resolveHumanDisplay(human.id, humans); + return ( + addMember(human.id)} + className="flex-row items-center gap-3 py-2.5 active:bg-white/5 rounded-lg" + > + + + + {display.displayName} + + + {display.email} + + + Add + + ); + })} + + ) : null} + + + ); +} + +function ModePill({ + active, + icon, + label, + onPress, +}: { + active: boolean; + icon: React.ReactNode; + label: string; + onPress: () => void; +}) { + return ( + + {icon} + + {label} + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamMetadataHeader.tsx b/js/mobile/src/features/stream-view/StreamMetadataHeader.tsx new file mode 100644 index 0000000..d756cdd --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamMetadataHeader.tsx @@ -0,0 +1,64 @@ +import { Text, View } from "react-native"; +import type { Network, Particle } from "@/api/types"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { RelativeTimestamp } from "@/components/RelativeTimestamp"; +import { Avatar } from "@/components/Avatar"; +import { useStreamPresence } from "./stream-presence-context"; + +interface StreamMetadataHeaderProps { + particle: Particle | null; + network: Network | null; +} + +/** + * Avatar + display name + relative time. Sits below the segmented bar so the + * "who/when" answer is always one glance away — Snapchat-style. + */ +export function StreamMetadataHeader({ + particle, + network, +}: StreamMetadataHeaderProps) { + const { onlineHumanIds } = useStreamPresence(); + if (!particle) return null; + const display = resolveHumanDisplay( + particle.created_by_human_id, + network?.humans, + ); + + const editedAt = + particle.type === "text" ? particle.properties.edited_at : undefined; + const isOnline = particle.created_by_human_id + ? onlineHumanIds.has(particle.created_by_human_id) + : false; + + return ( + + + + + {display.displayName} + + + + {editedAt ? ( + + · edited{" "} + + + ) : null} + + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamTopActions.tsx b/js/mobile/src/features/stream-view/StreamTopActions.tsx new file mode 100644 index 0000000..c335658 --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamTopActions.tsx @@ -0,0 +1,110 @@ +import { Pressable, Text, View } from "react-native"; +import { EllipsisVertical, Globe, Maximize2, Minimize2 } from "lucide-react-native"; +import type { Human, Particle } from "@/api/types"; +import { parseVisibleTo } from "@/lib/stream-visibility"; +import { cn } from "@/lib/utils"; +import { Avatar } from "@/components/Avatar"; +import { useStreamPresence } from "./stream-presence-context"; + +interface StreamTopActionsProps { + networkId: string; + streamParticle: Particle & { type: "stream" }; + humans: Human[]; + videoFit: "cover" | "contain"; + onToggleVideoFit: () => void; + onOpenMembers: () => void; + onOpenActions: () => void; + /** True when current particle is a video — fit toggle hidden otherwise. */ + showFitToggle: boolean; +} + +const MAX_AVATARS = 3; + +/** + * Top-right cluster on StreamView: visibility avatars (with presence ring), + * fit/fill toggle, and actions menu trigger. Mirrors desktop's stream-top-bar + * but compact for the mobile chrome. + */ +export function StreamTopActions({ + networkId, + streamParticle, + humans, + videoFit, + onToggleVideoFit, + onOpenMembers, + onOpenActions, + showFitToggle, +}: StreamTopActionsProps) { + const { onlineHumanIds } = useStreamPresence(); + const visibility = parseVisibleTo(streamParticle.visible_to, networkId); + const memberIds = + visibility.mode === "network" + ? humans.map((h) => h.id) + : visibility.humanIds; + const shown = memberIds.slice(0, MAX_AVATARS); + const overflow = memberIds.length - shown.length; + + return ( + + + {visibility.mode === "network" && memberIds.length === 0 ? ( + + ) : ( + + {shown.map((id, idx) => ( + + {/* The stack ring matches the chrome's translucent bg so it + reads as a separator without painting hard black halos. */} + + + ))} + + )} + {overflow > 0 ? ( + + +{overflow} + + ) : null} + + + {showFitToggle ? ( + + {videoFit === "cover" ? ( + + ) : ( + + )} + + ) : null} + + + + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamView.tsx b/js/mobile/src/features/stream-view/StreamView.tsx new file mode 100644 index 0000000..c35d364 --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamView.tsx @@ -0,0 +1,646 @@ +import { useCallback, useEffect, useState } from "react"; +import { Alert, Dimensions, Pressable, Text, View } from "react-native"; +import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context"; +import { StatusBar } from "expo-status-bar"; +import * as Haptics from "expo-haptics"; +import { + Gesture, + GestureDetector, +} from "react-native-gesture-handler"; +import Animated, { + Extrapolation, + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; +import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg"; +import { isParticleDeleted, type Particle } from "@/api/types"; +import { + parseParticlePath, + particlePath, + toFirestoreDocPath, + type ParticlePath, +} from "@/lib/particle-path"; +import { + softDeleteParticle, + toggleParticleReaction, + updateStreamStatus, +} from "@/lib/firestore-particles"; +import { toast } from "sonner-native"; +import { toUserMessage } from "@/lib/errors"; +import { useNetwork } from "@/hooks/use-networks"; +import { useStreamPlayback } from "@/hooks/use-stream-playback"; +import { useSuspendPlayback } from "@/hooks/use-suspend-playback"; +import { + selectIsComposing, + selectIsPaused, + usePlaybackPauseStore, +} from "@/stores/playback-pause-store"; +import { useAuthStore } from "@/stores/auth-store"; +import { ComposeDock } from "@/features/compose/ComposeDock"; +import { ComposingIndicator } from "@/components/ComposingIndicator"; +import { PlaybackPageIndicator } from "./PlaybackPageIndicator"; +import { ReactionSheet } from "./ReactionSheet"; +import { StreamMetadataHeader } from "./StreamMetadataHeader"; +import { StreamSafeAreaProvider } from "./stream-safe-area"; +import { + StreamPresenceProvider, + useStreamComposing, +} from "./stream-presence-context"; +import { TextParticleView } from "./TextParticleView"; +import { MediaParticleView } from "./MediaParticleView"; +import { DeletedParticleView } from "./DeletedParticleView"; +import { FallbackParticleView } from "./FallbackParticleView"; +import { useExitCountdown } from "./use-exit-countdown"; +import { StreamTopActions } from "./StreamTopActions"; +import { StreamActionsSheet, type StreamActionId } from "./StreamActionsSheet"; +import { StreamMembersSheet } from "./StreamMembersSheet"; +import { RenameStreamSheet } from "./RenameStreamSheet"; +import { ReactionStack } from "./ReactionStack"; + +const SCREEN_HEIGHT = Dimensions.get("window").height; +// Tap-zone split: left 28% goes back, right 72% goes forward — matching the +// asymmetric "Snapchat thumb-zone" so right-handed taps default to forward. +const PREV_ZONE_RATIO = 0.28; +// Swipe-down dismiss commit thresholds — either move 1/4 of the screen, or +// flick downward fast enough. +const DISMISS_DISTANCE = SCREEN_HEIGHT * 0.25; +const DISMISS_VELOCITY = 900; +// Swipe-up reactions commit thresholds — flick up ~80px or with enough velocity. +const REACTIONS_DISTANCE = 80; +const REACTIONS_VELOCITY = 600; +// Approx height of the ComposeDock from the screen bottom (record button stack +// + pb-10). Status pills sit just above this so they aren't hidden behind it. +const COMPOSE_DOCK_HEIGHT = 50; + +interface StreamViewProps { + streamParticle: Particle & { type: "stream" }; + path: ParticlePath; + onExit: () => void; +} + +export function StreamView(props: StreamViewProps) { + const { networkId } = parseParticlePath(props.path); + // The presence provider wraps the inner view so any descendant can broadcast + // composing state without re-deriving the channel id. + return ( + + + + ); +} + +function StreamViewInner({ streamParticle, path, onExit }: StreamViewProps) { + const { networkId } = parseParticlePath(path); + const network = useNetwork(networkId); + const insets = useSafeAreaInsets(); + const { composingUsers } = useStreamComposing(); + + const { children, currentParticle, currentIndex, status, next, prev } = + useStreamPlayback(streamParticle, path); + + const paused = usePlaybackPauseStore(selectIsPaused); + const composing = usePlaybackPauseStore(selectIsComposing); + const [progress, setProgress] = useState(0); + const userId = useAuthStore((s) => s.user?.id) ?? ""; + + // Local hold state drives the "touch-hold" pause suspender. We wrap the JS + // setter inside a runOnJS callback dispatched from the worklet thread. + const [holdActive, setHoldActive] = useState(false); + useSuspendPlayback(holdActive, "touch-hold"); + + // Reaction sheet — opens via swipe-up on the canvas. + const [reactionsOpen, setReactionsOpen] = useState(false); + + // Top-right cluster sheet state. `videoFit` lets the user toggle expo-video's + // contentFit for the active media particle when desktop captures of unusual + // aspect ratios get cropped uncomfortably under the default `cover` mode. + const [actionsOpen, setActionsOpen] = useState(false); + const [membersOpen, setMembersOpen] = useState(false); + const [renameOpen, setRenameOpen] = useState(false); + const [videoFit, setVideoFit] = useState<"cover" | "contain">("cover"); + + const isCreator = !!userId && userId === streamParticle.created_by_human_id; + const canDeleteCurrentParticle = + !!currentParticle && + !!userId && + currentParticle.created_by_human_id === userId && + currentParticle.type !== "stream" && + currentParticle.type !== "folder" && + !isParticleDeleted(currentParticle); + + const showFitToggle = + !!currentParticle && + !isParticleDeleted(currentParticle) && + currentParticle.type === "media" && + !currentParticle.properties.mime_type.startsWith("audio/"); + + const handleStreamAction = useCallback( + async (action: StreamActionId) => { + const streamDocPath = toFirestoreDocPath( + particlePath(networkId, [streamParticle.id]), + ); + switch (action) { + case "toggle-status": { + try { + await updateStreamStatus( + streamDocPath, + streamParticle.status === "open" ? "closed" : "open", + ); + } catch (err) { + toast.error(toUserMessage(err)); + } + return; + } + case "rename": + setRenameOpen(true); + return; + case "members": + setMembersOpen(true); + return; + case "delete-particle": { + if (!currentParticle || !userId) return; + if (!canDeleteCurrentParticle) return; + Alert.alert( + "Delete this particle?", + "This cannot be undone. Other viewers will see a \"deleted\" message in its place.", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + try { + const docPath = toFirestoreDocPath( + particlePath(networkId, [ + streamParticle.id, + currentParticle.id, + ]), + ); + await softDeleteParticle(docPath, userId); + } catch (err) { + toast.error(toUserMessage(err)); + } + }, + }, + ], + ); + return; + } + } + }, + [ + networkId, + streamParticle.id, + streamParticle.status, + currentParticle, + userId, + canDeleteCurrentParticle, + ], + ); + + const reactionsOnCurrent = + currentParticle && !isParticleDeleted(currentParticle) + ? currentParticle.type === "media" || currentParticle.type === "text" + ? currentParticle.reactions + : undefined + : undefined; + + const handleToggleReaction = useCallback( + (key: string) => { + if (!userId || !currentParticle) return; + if (isParticleDeleted(currentParticle)) return; + const docPath = toFirestoreDocPath( + particlePath(networkId, [streamParticle.id, currentParticle.id]), + ); + void toggleParticleReaction( + docPath, + key, + userId, + reactionsOnCurrent, + ); + }, + [userId, currentParticle, networkId, streamParticle.id, reactionsOnCurrent], + ); + + const openReactions = useCallback(() => setReactionsOpen(true), []); + + // Reset progress whenever the active particle changes. + useEffect(() => { + setProgress(0); + }, [currentParticle?.id]); + + const handleTap = useCallback( + (xRatio: number) => { + if (xRatio < PREV_ZONE_RATIO) { + if (currentIndex <= 0) { + // Soft "thud" — nothing to go back to. + void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + return; + } + prev(); + } else { + next(); + } + }, + [currentIndex, next, prev], + ); + + // --- Swipe-down dismiss --- + const translateY = useSharedValue(0); + const screenWidth = Dimensions.get("window").width; + + const exit = useCallback(() => { + onExit(); + }, [onExit]); + + const panDown = Gesture.Pan() + .activeOffsetY(15) + .failOffsetX([-30, 30]) + .failOffsetY(-20) + .onUpdate((e) => { + "worklet"; + translateY.value = Math.max(0, e.translationY); + }) + .onEnd((e) => { + "worklet"; + if ( + e.translationY > DISMISS_DISTANCE || + e.velocityY > DISMISS_VELOCITY + ) { + translateY.value = withTiming(SCREEN_HEIGHT, { duration: 220 }); + runOnJS(exit)(); + } else { + translateY.value = withSpring(0, { + damping: 22, + stiffness: 220, + mass: 0.6, + }); + } + }); + + // Swipe-up opens the reaction sheet. Mirror the down pan's discipline — + // fail on horizontal motion so it doesn't fight the tap-zones. + const panUp = Gesture.Pan() + .activeOffsetY(-15) + .failOffsetX([-30, 30]) + .failOffsetY(20) + .onEnd((e) => { + "worklet"; + if ( + e.translationY < -REACTIONS_DISTANCE || + e.velocityY < -REACTIONS_VELOCITY + ) { + runOnJS(openReactions)(); + } + }); + + // --- Tap (advance / regress) --- + const tap = Gesture.Tap() + .maxDuration(180) + .maxDistance(15) + .onEnd((e, success) => { + "worklet"; + if (!success) return; + const ratio = e.x / screenWidth; + runOnJS(handleTap)(ratio); + }); + + // --- Long-press (hold-to-pause) --- + const longPress = Gesture.LongPress() + .minDuration(180) + .maxDistance(15) + .onStart(() => { + "worklet"; + runOnJS(setHoldActive)(true); + }) + .onTouchesUp(() => { + "worklet"; + runOnJS(setHoldActive)(false); + }) + .onFinalize(() => { + "worklet"; + runOnJS(setHoldActive)(false); + }); + + // Pan-down (dismiss), pan-up (reactions), and tap+longPress race against + // each other. The first to clear its activeOffsetY wins; the others fail. + const composed = Gesture.Race( + panDown, + panUp, + Gesture.Simultaneous(tap, longPress), + ); + + const containerStyle = useAnimatedStyle(() => { + const opacity = interpolate( + translateY.value, + [0, SCREEN_HEIGHT * 0.5], + [1, 0.4], + Extrapolation.CLAMP, + ); + const scale = interpolate( + translateY.value, + [0, SCREEN_HEIGHT], + [1, 0.85], + Extrapolation.CLAMP, + ); + return { + transform: [{ translateY: translateY.value }, { scale }], + opacity, + }; + }); + + const backdropStyle = useAnimatedStyle(() => { + const opacity = interpolate( + translateY.value, + [0, SCREEN_HEIGHT * 0.5], + [1, 0.6], + Extrapolation.CLAMP, + ); + return { opacity }; + }); + + // --- End-of-stream countdown --- + const exitRemainingMs = useExitCountdown(status, paused, exit); + + // Chrome reservations: top = safe-area + segmented bar (3) + gap (12) + + // metadata row (~38) + breathing room (12). Bottom = safe-area + room for + // pause / countdown pills + the compose dock that lands in this same step. + const chromeTop = insets.top + 65; + const chromeBottom = insets.bottom + 96; + + // --- Render the active particle --- + const renderParticle = (particle: Particle) => { + if (isParticleDeleted(particle)) { + return ( + + ); + } + switch (particle.type) { + case "text": + return ( + + ); + case "media": + return ( + + ); + default: + return ( + + ); + } + }; + + // --- Content guards --- + if (children.length === 0) { + return ( + + + ); + } + + return ( + + + ); +} diff --git a/js/mobile/src/features/stream-view/StreamViewScreen.tsx b/js/mobile/src/features/stream-view/StreamViewScreen.tsx new file mode 100644 index 0000000..e400596 --- /dev/null +++ b/js/mobile/src/features/stream-view/StreamViewScreen.tsx @@ -0,0 +1,48 @@ +import { ActivityIndicator, Pressable, Text, View } from "react-native"; +import { StatusBar } from "expo-status-bar"; +import type { RootStackScreenProps } from "@/navigation/types"; +import { particlePath } from "@/lib/particle-path"; +import { useLiveParticle } from "@/hooks/use-particle"; +import { StreamView } from "./StreamView"; + +export function StreamViewScreen({ + navigation, + route, +}: RootStackScreenProps<"StreamView">) { + const { networkId, streamId } = route.params; + const streamPath = particlePath(networkId, [streamId]); + const { particle, isLoading, error } = useLiveParticle(streamPath); + + if (isLoading && !particle) { + return ( + + + ); + } + + if (error || !particle || particle.type !== "stream") { + return ( + + + ); + } + + return ( + navigation.goBack()} + /> + ); +} diff --git a/js/mobile/src/features/stream-view/TextParticleView.tsx b/js/mobile/src/features/stream-view/TextParticleView.tsx new file mode 100644 index 0000000..f4aead1 --- /dev/null +++ b/js/mobile/src/features/stream-view/TextParticleView.tsx @@ -0,0 +1,114 @@ +import { useEffect, useRef } from "react"; +import { ScrollView, Text, View } from "react-native"; +import type { Particle } from "@/api/types"; +import { cn } from "@/lib/utils"; +import { useStreamSafeArea } from "./stream-safe-area"; + +type TextParticle = Extract; + +interface TextParticleViewProps { + particle: TextParticle; + paused: boolean; + onEnded: () => void; + onProgress: (ratio: number) => void; +} + +// Mirrors desktop's read-duration math (chars/min ≈ 1000, plus +2s per +// link/attachment, clamped 3–15s). Mobile v1 has no attachments and we +// don't extract link previews mid-render, so the formula collapses to +// a length-only base. +const CHARS_PER_MINUTE = 1000; +const MIN_DURATION_S = 3; +const MAX_DURATION_S = 15; +const TICK_MS = 100; +const IMMERSIVE_CHAR_LIMIT = 120; + +function computeReadDuration(text: string): number { + const base = (text.length / CHARS_PER_MINUTE) * 60; + return Math.min(Math.max(base, MIN_DURATION_S), MAX_DURATION_S); +} + +function getImmersiveStyle(length: number) { + if (length < 30) + return { className: "text-5xl font-semibold leading-tight" }; + if (length < 70) + return { className: "text-3xl font-semibold leading-snug" }; + return { className: "text-2xl font-normal leading-snug" }; +} + +export function TextParticleView({ + particle, + paused, + onEnded, + onProgress, +}: TextParticleViewProps) { + const content = particle.properties.content; + const durationS = computeReadDuration(content); + const elapsedRef = useRef(0); + const safe = useStreamSafeArea(); + + // Reset when the particle changes. + useEffect(() => { + elapsedRef.current = 0; + onProgress(0); + }, [particle.id, onProgress]); + + useEffect(() => { + if (paused) return; + + const interval = setInterval(() => { + elapsedRef.current += TICK_MS / 1000; + const ratio = Math.min(elapsedRef.current / durationS, 1); + onProgress(ratio); + if (ratio >= 1) { + clearInterval(interval); + onEnded(); + } + }, TICK_MS); + + return () => clearInterval(interval); + }, [paused, durationS, onEnded, onProgress, particle.id]); + + // Immersive (short, plain): centered, large type — feels like a lock-screen note. + if (content.length < IMMERSIVE_CHAR_LIMIT) { + const style = getImmersiveStyle(content.length); + return ( + + + {content} + + + ); + } + + // Long text: scrollable card so the reader can pace themselves; the + // duration timer keeps ticking either way, which is intentional — + // long messages should still auto-advance at the 15s cap. Padding is + // pulled from the StreamSafeArea so the card never slips under chrome. + return ( + + + {content} + + + ); +} diff --git a/js/mobile/src/features/stream-view/stream-presence-context.tsx b/js/mobile/src/features/stream-view/stream-presence-context.tsx new file mode 100644 index 0000000..9b8e1df --- /dev/null +++ b/js/mobile/src/features/stream-view/stream-presence-context.tsx @@ -0,0 +1,203 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from "react"; +import { useChannel } from "@/hooks/use-channel"; +import { useAuthStore } from "@/stores/auth-store"; + +export type ComposingMode = "recording" | "typing" | "screen"; + +export interface ComposingUser { + humanId: string; + mode: ComposingMode; + lastSeen: number; +} + +interface StreamPresenceContextValue { + onlineHumanIds: Set; + composingUsers: ComposingUser[]; + startComposing: (mode: ComposingMode) => void; + stopComposing: () => void; +} + +const COMPOSING_TIMEOUT_MS = 10_000; +const COMPOSING_HEARTBEAT_MS = 5_000; +const COMPOSING_CLEANUP_INTERVAL_MS = 2_000; + +const StreamPresenceContext = createContext( + null, +); + +interface StreamPresenceProviderProps { + networkId: string; + streamId: string; + children: ReactNode; +} + +export function StreamPresenceProvider({ + networkId, + streamId, + children, +}: StreamPresenceProviderProps) { + const channelId = `stream:${networkId}:${streamId}`; + const { presence, messages, sendMessage } = useChannel(channelId); + const currentUserId = useAuthStore((s) => s.user?.id); + + const onlineHumanIds = useMemo(() => new Set(presence), [presence]); + + // --- Composing state --- + const [composingUsers, setComposingUsers] = useState([]); + const composingMapRef = useRef(new Map()); + const processedCountRef = useRef(0); + + // Process new messages incrementally — slicing the messages array means + // we don't re-scan the whole history every render. + useEffect(() => { + if (messages.length <= processedCountRef.current) return; + + const newMessages = messages.slice(processedCountRef.current); + processedCountRef.current = messages.length; + + let changed = false; + const map = composingMapRef.current; + + for (const msg of newMessages) { + const payload = msg.payload as + | { type: string; mode?: string } + | undefined; + if (!payload?.type) continue; + if (msg.humanId === currentUserId) continue; + + if (payload.type === "composing_start" && payload.mode) { + map.set(msg.humanId, { + humanId: msg.humanId, + mode: payload.mode as ComposingMode, + lastSeen: Date.now(), + }); + changed = true; + } else if (payload.type === "composing_stop") { + if (map.delete(msg.humanId)) changed = true; + } + } + + if (changed) { + setComposingUsers(Array.from(map.values())); + } + }, [messages, currentUserId]); + + // Drop composing entries when a user leaves the channel — covers the + // "they backgrounded the app without sending stop" case. + useEffect(() => { + const map = composingMapRef.current; + const onlineSet = new Set(presence); + let changed = false; + + for (const humanId of map.keys()) { + if (!onlineSet.has(humanId)) { + map.delete(humanId); + changed = true; + } + } + + if (changed) { + setComposingUsers(Array.from(map.values())); + } + }, [presence]); + + // Sweep stale composing entries (last heartbeat > 10s ago). + useEffect(() => { + const interval = setInterval(() => { + const map = composingMapRef.current; + const now = Date.now(); + let changed = false; + + for (const [humanId, entry] of map) { + if (now - entry.lastSeen > COMPOSING_TIMEOUT_MS) { + map.delete(humanId); + changed = true; + } + } + + if (changed) { + setComposingUsers(Array.from(map.values())); + } + }, COMPOSING_CLEANUP_INTERVAL_MS); + + return () => clearInterval(interval); + }, []); + + // --- Composing broadcast --- + const heartbeatRef = useRef | undefined>( + undefined, + ); + + const startComposing = useCallback( + (mode: ComposingMode) => { + sendMessage({ type: "composing_start", mode }); + if (heartbeatRef.current) clearInterval(heartbeatRef.current); + heartbeatRef.current = setInterval(() => { + sendMessage({ type: "composing_start", mode }); + }, COMPOSING_HEARTBEAT_MS); + }, + [sendMessage], + ); + + const stopComposing = useCallback(() => { + if (heartbeatRef.current) clearInterval(heartbeatRef.current); + heartbeatRef.current = undefined; + sendMessage({ type: "composing_stop" }); + }, [sendMessage]); + + useEffect(() => { + return () => { + if (heartbeatRef.current) clearInterval(heartbeatRef.current); + }; + }, []); + + const value = useMemo( + () => ({ + onlineHumanIds, + composingUsers, + startComposing, + stopComposing, + }), + [onlineHumanIds, composingUsers, startComposing, stopComposing], + ); + + return ( + + {children} + + ); +} + +function useStreamPresenceContext() { + const ctx = useContext(StreamPresenceContext); + if (!ctx) { + throw new Error( + "useStreamPresence must be used within a StreamPresenceProvider", + ); + } + return ctx; +} + +export function useStreamPresence() { + const { onlineHumanIds } = useStreamPresenceContext(); + return { onlineHumanIds }; +} + +export function useStreamComposing() { + const { composingUsers } = useStreamPresenceContext(); + return { composingUsers }; +} + +export function useStreamComposingBroadcast() { + const { startComposing, stopComposing } = useStreamPresenceContext(); + return { startComposing, stopComposing }; +} diff --git a/js/mobile/src/features/stream-view/stream-safe-area.tsx b/js/mobile/src/features/stream-view/stream-safe-area.tsx new file mode 100644 index 0000000..6e27287 --- /dev/null +++ b/js/mobile/src/features/stream-view/stream-safe-area.tsx @@ -0,0 +1,28 @@ +import { createContext, useContext, type ReactNode } from "react"; + +interface StreamSafeArea { + /** Pixels from the screen top reserved for the segmented bar + metadata. */ + top: number; + /** Pixels from the screen bottom reserved for compose dock + pills. */ + bottom: number; +} + +const Ctx = createContext({ top: 0, bottom: 0 }); + +/** + * Lets particle views know how much vertical space the chrome reserves so + * scrollable content (long text, future inboxes) doesn't slip under the + * segmented bar / compose dock. Defaults to 0/0 so views work outside the + * StreamView shell (e.g. in a preview). + */ +export function StreamSafeAreaProvider({ + top, + bottom, + children, +}: StreamSafeArea & { children: ReactNode }) { + return {children}; +} + +export function useStreamSafeArea(): StreamSafeArea { + return useContext(Ctx); +} diff --git a/js/mobile/src/features/stream-view/use-exit-countdown.ts b/js/mobile/src/features/stream-view/use-exit-countdown.ts new file mode 100644 index 0000000..2d60735 --- /dev/null +++ b/js/mobile/src/features/stream-view/use-exit-countdown.ts @@ -0,0 +1,48 @@ +import { useEffect, useState } from "react"; +import { useEvent } from "@/hooks/use-event"; + +export const EXIT_DELAY_MS = 5000; +export const EXIT_TICK_MS = 100; + +type PlaybackStatus = "idle" | "playing" | "ended"; + +/** + * Returns the remaining ms when the stream has ended, or null otherwise. + * Pauses while `paused` is true (compose, hold-to-pause, swipe-down…). + */ +export function useExitCountdown( + status: PlaybackStatus, + paused: boolean, + onExit: () => void, +): number | null { + const [remainingMs, setRemainingMs] = useState(null); + const handleExit = useEvent(onExit); + + useEffect(() => { + if (status === "ended") { + setRemainingMs(EXIT_DELAY_MS); + } else { + setRemainingMs(null); + } + }, [status]); + + useEffect(() => { + if (remainingMs === null || remainingMs <= 0 || paused) return; + const interval = setInterval(() => { + setRemainingMs((prev) => { + if (prev === null) return null; + const next = prev - EXIT_TICK_MS; + return next <= 0 ? 0 : next; + }); + }, EXIT_TICK_MS); + return () => clearInterval(interval); + }, [remainingMs !== null && remainingMs > 0, paused, remainingMs]); + + useEffect(() => { + if (remainingMs !== null && remainingMs <= 0) { + handleExit(); + } + }, [remainingMs, handleExit]); + + return remainingMs; +} diff --git a/js/mobile/src/features/streams/NewStreamScreen.tsx b/js/mobile/src/features/streams/NewStreamScreen.tsx new file mode 100644 index 0000000..2be3492 --- /dev/null +++ b/js/mobile/src/features/streams/NewStreamScreen.tsx @@ -0,0 +1,206 @@ +import { useMemo, useState } from "react"; +import { + KeyboardAvoidingView, + Platform, + Pressable, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { StatusBar } from "expo-status-bar"; +import { ChevronRight, Globe, Lock, X } from "lucide-react-native"; +import { toast } from "sonner-native"; +import { ComposeDock } from "@/features/compose/ComposeDock"; +import { useNetwork } from "@/hooks/use-networks"; +import { particlePath } from "@/lib/particle-path"; +import { generateRandomName } from "@/lib/random-name"; +import { createStreamWithFirstParticle } from "@/lib/upload"; +import { toUserMessage } from "@/lib/errors"; +import { + buildNetworkVisibility, + parseVisibleTo, +} from "@/lib/stream-visibility"; +import { useAuthStore } from "@/stores/auth-store"; +import type { RootStackScreenProps } from "@/navigation/types"; +import { VisibilityPickerSheet } from "./VisibilityPickerSheet"; + +const STREAM_NAME_MAX = 60; + +/** + * Top-level stream creation. The user names the stream, picks visibility, and + * composes the first particle on one screen — desktop's compose-overlay flow + * collapsed into a touch-native single page. + */ +export function NewStreamScreen({ + route, + navigation, +}: RootStackScreenProps<"NewStream">) { + const { networkId } = route.params; + const network = useNetwork(networkId); + const userId = useAuthStore((s) => s.user?.id); + + const suggestion = useMemo(() => generateRandomName(), []); + const [name, setName] = useState(""); + const [visibleTo, setVisibleTo] = useState(() => + buildNetworkVisibility(networkId), + ); + const [pickerOpen, setPickerOpen] = useState(false); + + const effectiveName = name.trim() || suggestion; + + const handleStreamCreated = (streamId: string) => { + navigation.replace("StreamView", { networkId, streamId }); + }; + + const submitText = async (content: string) => { + if (!userId) throw new Error("Not signed in."); + try { + const { streamId } = await createStreamWithFirstParticle({ + networkId, + name: effectiveName, + visibleTo, + createdByHumanId: userId, + firstParticle: { type: "text", content }, + }); + handleStreamCreated(streamId); + } catch (err) { + toast.error(toUserMessage(err)); + throw err; + } + }; + + const submitMedia = async ({ + fileUri, + mimeType, + durationMs, + source, + }: { + fileUri: string; + mimeType: string; + durationMs: number; + source: "camera" | "screen"; + }) => { + if (!userId) throw new Error("Not signed in."); + try { + const { streamId } = await createStreamWithFirstParticle({ + networkId, + name: effectiveName, + visibleTo, + createdByHumanId: userId, + firstParticle: { + type: "media", + fileUri, + mimeType, + durationMs, + source, + }, + }); + handleStreamCreated(streamId); + } catch (err) { + toast.error(toUserMessage(err)); + throw err; + } + }; + + const placeholderPath = particlePath(networkId, []); + + const visibility = parseVisibleTo(visibleTo, networkId); + const visibleSummary = + visibility.mode === "network" + ? `Everyone in ${network?.name ?? "this network"}` + : `${visibility.humanIds.length} ${ + visibility.humanIds.length === 1 ? "person" : "people" + }`; + + return ( + + + + + + navigation.goBack()} + hitSlop={12} + accessibilityLabel="Cancel" + > + + + + New stream + + + + + + + + + Name + + setName(v.slice(0, STREAM_NAME_MAX))} + placeholder={suggestion} + placeholderTextColor="rgba(255,255,255,0.35)" + autoCapitalize="none" + autoCorrect={false} + maxLength={STREAM_NAME_MAX} + className="bg-white/10 rounded-xl px-4 py-3 text-white text-lg" + /> + + + Visible to + + setPickerOpen(true)} + className="bg-white/10 active:bg-white/15 rounded-xl px-4 py-3 flex-row items-center gap-3" + > + {visibility.mode === "network" ? ( + + ) : ( + + )} + + {visibleSummary} + + + + + + + Hold the button below to record a voice or video message — that's + the first particle in your new stream. + + + + + + + + setPickerOpen(false)} + networkId={networkId} + networkName={network?.name} + humans={network?.humans ?? []} + selfHumanId={userId} + visibleTo={visibleTo} + onChange={setVisibleTo} + /> + + ); +} diff --git a/js/mobile/src/features/streams/StreamCard.tsx b/js/mobile/src/features/streams/StreamCard.tsx new file mode 100644 index 0000000..782d979 --- /dev/null +++ b/js/mobile/src/features/streams/StreamCard.tsx @@ -0,0 +1,156 @@ +import { memo, useMemo } from "react"; +import { Pressable, Text, View } from "react-native"; +import type { Particle, StreamProperties } from "@/api/types"; +import { isParticleDeleted } from "@/api/types"; +import { RelativeTimestamp } from "@/components/RelativeTimestamp"; +import { useLiveLatestChild } from "@/hooks/use-particle"; +import { useNetwork } from "@/hooks/use-networks"; +import { particlePath } from "@/lib/particle-path"; +import { cn, getInitials } from "@/lib/utils"; +import { useAuthStore } from "@/stores/auth-store"; + +interface StreamCardProps { + particle: Particle & { type: "stream"; properties: StreamProperties }; + networkId: string; + onPress: () => void; +} + +/** + * Mobile counterpart of js/desktop/src/features/particles/stream-card.tsx — + * same data wiring (subscribe to the latest child for unread + initials), + * touch-tuned layout (single row, no preview thumbnail in v1). + */ +export const StreamCard = memo(function StreamCard({ + particle, + networkId, + onPress, +}: StreamCardProps) { + const streamPath = particlePath(networkId, [particle.id]); + const { latestChild } = useLiveLatestChild(streamPath); + const userId = useAuthStore((s) => s.user?.id) ?? ""; + const network = useNetwork(networkId); + + const isDM = + particle.visible_to.length === 2 && + particle.visible_to.every((v) => v.startsWith("human:")); + + const initials = useMemo(() => { + if (isDM) { + const otherEntry = particle.visible_to.find( + (v) => v !== `human:${userId}`, + ); + if (otherEntry) { + const otherId = otherEntry.replace("human:", ""); + const otherHuman = network?.humans?.find((h) => h.id === otherId); + if (otherHuman) return getInitials(otherHuman.email); + } + } + + if (latestChild) { + const creator = network?.humans?.find( + (h) => h.id === latestChild.created_by_human_id, + ); + if (creator) return getInitials(creator.email); + } + + return particle.properties.name.slice(0, 2).toUpperCase(); + }, [ + isDM, + particle.visible_to, + particle.properties.name, + userId, + latestChild, + network, + ]); + + const isUnseen = useMemo(() => { + if (!latestChild) return false; + const latestChildTimestamp = latestChild.created_at.getTime(); + const userPlaybackPosition = + particle.playback_markers?.[userId]?.getTime() ?? 0; + return latestChildTimestamp > userPlaybackPosition; + }, [latestChild, particle.playback_markers, userId]); + + const previewLabel = useMemo(() => { + if (!latestChild) return "No messages yet"; + if (isParticleDeleted(latestChild)) return "Message deleted"; + switch (latestChild.type) { + case "media": + return latestChild.properties.mime_type.startsWith("audio/") + ? "Voice message" + : "Video message"; + case "text": + return latestChild.properties.content; + case "file": + return latestChild.properties.filename; + case "quest": + return latestChild.properties.title; + case "paper": + return latestChild.properties.title; + default: + return "Update"; + } + }, [latestChild]); + + return ( + + + + {initials} + + + + + + {particle.properties.name} + + + {previewLabel} + + + + + {latestChild ? ( + + ) : null} + {isUnseen ? ( + + ) : null} + + + ); +}); diff --git a/js/mobile/src/features/streams/StreamListScreen.tsx b/js/mobile/src/features/streams/StreamListScreen.tsx new file mode 100644 index 0000000..2f64a9f --- /dev/null +++ b/js/mobile/src/features/streams/StreamListScreen.tsx @@ -0,0 +1,140 @@ +import { + ActivityIndicator, + FlatList, + Pressable, + Text, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { toUserMessage } from "@/lib/errors"; +import { particlePath } from "@/lib/particle-path"; +import { useNetwork } from "@/hooks/use-networks"; +import { useStreamParticles } from "@/hooks/use-stream-particles"; +import type { RootStackScreenProps } from "@/navigation/types"; +import { StreamCard } from "./StreamCard"; + +export function StreamListScreen({ + route, + navigation, +}: RootStackScreenProps<"StreamList">) { + const { networkId } = route.params; + const network = useNetwork(networkId); + + const path = particlePath(networkId, []); + + const { streams, isLoading, error } = useStreamParticles(path, { + status: "open", + }); + + return ( + +

navigation.goBack()} + /> + + {error ? ( + + ) : isLoading && streams.length === 0 ? ( + + ) : streams.length === 0 ? ( + + ) : ( + s.id} + contentContainerClassName="" + renderItem={({ item }) => ( + + navigation.navigate("StreamView", { + networkId, + streamId: item.id, + }) + } + /> + )} + /> + )} + + navigation.navigate("NewStream", { networkId })} + /> + + ); +} + +function Header({ + title, + onBack, +}: { + title: string; + onBack: () => void; +}) { + return ( + + + + + + {title} + + + + ); +} + +function LoadingState() { + return ( + + + + ); +} + +function EmptyState() { + return ( + + + No streams yet. + + + Tap the button below to start one — voice, video, or text. + + + ); +} + +function ErrorState({ message }: { message: string }) { + return ( + + {message} + + Streams reconnect automatically once the network is back. + + + ); +} + +function ComposeFab({ onPress }: { onPress: () => void }) { + return ( + + + + + + + ); +} diff --git a/js/mobile/src/features/streams/VisibilityPickerSheet.tsx b/js/mobile/src/features/streams/VisibilityPickerSheet.tsx new file mode 100644 index 0000000..4bbc9b1 --- /dev/null +++ b/js/mobile/src/features/streams/VisibilityPickerSheet.tsx @@ -0,0 +1,218 @@ +import { useEffect, useMemo, useState } from "react"; +import { Pressable, ScrollView, Text, View } from "react-native"; +import { Check, Globe, Lock, X } from "lucide-react-native"; +import type { Human } from "@/api/types"; +import { cn } from "@/lib/utils"; +import { resolveHumanDisplay } from "@/lib/humans"; +import { + buildCustomVisibility, + buildNetworkVisibility, + parseVisibleTo, +} from "@/lib/stream-visibility"; +import { BottomSheet } from "@/components/BottomSheet"; +import { Avatar } from "@/components/Avatar"; + +interface VisibilityPickerSheetProps { + open: boolean; + onClose: () => void; + networkId: string; + networkName: string | undefined; + humans: Human[]; + selfHumanId: string | undefined; + visibleTo: string[]; + onChange: (visibleTo: string[]) => void; +} + +/** + * Touch-native visibility picker. Mirrors desktop's stream-members-overlay + * (network-wide vs. specific people) but as a bottom sheet that commits on + * close — the parent's `visibleTo` only updates when the user taps Done. + */ +export function VisibilityPickerSheet({ + open, + onClose, + networkId, + networkName, + humans, + selfHumanId, + visibleTo, + onChange, +}: VisibilityPickerSheetProps) { + const initial = useMemo( + () => parseVisibleTo(visibleTo, networkId), + [visibleTo, networkId], + ); + + const [mode, setMode] = useState<"network" | "custom">(initial.mode); + const [selected, setSelected] = useState>( + () => new Set(initial.mode === "custom" ? initial.humanIds : []), + ); + + useEffect(() => { + if (!open) return; + setMode(initial.mode); + setSelected( + new Set(initial.mode === "custom" ? initial.humanIds : []), + ); + }, [open, initial]); + + const others = humans.filter((h) => h.id !== selfHumanId); + + const toggle = (id: string) => { + setSelected((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const commit = () => { + if (mode === "network") { + onChange(buildNetworkVisibility(networkId)); + } else { + const ids = selfHumanId + ? [selfHumanId, ...Array.from(selected)] + : Array.from(selected); + onChange(buildCustomVisibility(ids)); + } + onClose(); + }; + + const customCount = selected.size + (selfHumanId ? 1 : 0); + const canCommit = mode === "network" || customCount >= 2; + + return ( + + + + + + Visible to + + + Done + + + + + + + } + label="Everyone" + onPress={() => setMode("network")} + /> + } + label="Specific people" + onPress={() => setMode("custom")} + /> + + + + {mode === "network" ? ( + + + Everyone in {networkName ?? "this network"} can see this stream. + + + ) : ( + + {others.length === 0 ? ( + + You're the only member of this network. Invite people on desktop, + then come back to choose specific viewers. + + ) : ( + others.map((human) => { + const display = resolveHumanDisplay(human.id, humans); + const isSelected = selected.has(human.id); + return ( + toggle(human.id)} + className={cn( + "flex-row items-center gap-3 px-3 py-2.5 rounded-lg", + isSelected ? "bg-white/10" : "active:bg-white/5", + )} + > + + + + {display.displayName} + + + {display.email} + + + + {isSelected ? ( + + ) : null} + + + ); + }) + )} + + )} + + ); +} + +function ModePill({ + active, + icon, + label, + onPress, +}: { + active: boolean; + icon: React.ReactNode; + label: string; + onPress: () => void; +}) { + return ( + + {icon} + + {label} + + + ); +} diff --git a/js/mobile/src/firebase.ts b/js/mobile/src/firebase.ts new file mode 100644 index 0000000..a5bdba1 --- /dev/null +++ b/js/mobile/src/firebase.ts @@ -0,0 +1,30 @@ +import { initializeApp } from "firebase/app"; +import { + initializeAuth, + // `getReactNativePersistence` is documented Firebase RN setup but Firebase + // intentionally omits it from `firebase/auth`'s public type bundle (it + // would pollute web autocomplete). The runtime export exists on every + // platform; this is the workaround the Firebase docs themselves use. + // @ts-expect-error — RN-only symbol missing from public Firebase types. + getReactNativePersistence, +} from "firebase/auth"; +import { initializeFirestore } from "firebase/firestore"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { appConfig } from "@/config/env"; + +export const firebaseApp = initializeApp(appConfig.firebase); + +// AsyncStorage persists the Firebase auth token across cold starts. Without +// it, the user would have to re-sign-in to Firestore every launch even though +// the Orion bearer token is in SecureStore. +export const firebaseAuth = initializeAuth(firebaseApp, { + persistence: getReactNativePersistence(AsyncStorage), +}); + +// Firestore's default WebChannel transport often fails on cellular networks +// and behind aggressive proxies on iOS. Long-polling is the documented +// remedy for React Native and is also what the Firebase team recommends +// for mobile apps using the JS SDK. +export const firestoreDb = initializeFirestore(firebaseApp, { + experimentalAutoDetectLongPolling: true, +}); diff --git a/js/mobile/src/hooks/use-channel.ts b/js/mobile/src/hooks/use-channel.ts new file mode 100644 index 0000000..3cbc608 --- /dev/null +++ b/js/mobile/src/hooks/use-channel.ts @@ -0,0 +1,85 @@ +import { useCallback, useEffect, useState } from "react"; +import { usePusherClient } from "@/lib/pusher-provider"; +import type { ChannelMessage } from "@/lib/pusher-client"; + +interface UseChannelResult { + /** Current set of humanIds present in the channel */ + presence: string[]; + /** Messages received on this channel (since the hook mounted) */ + messages: ChannelMessage[]; + /** Send a message to the channel */ + sendMessage: (payload: unknown) => void; +} + +/** + * Subscribe to a pusher channel. Manages presence tracking and message delivery. + * Subscribes on mount, unsubscribes on unmount. + * + * @param channelId - The channel to subscribe to, or null to skip. + */ +export function useChannel(channelId: string | null): UseChannelResult { + const client = usePusherClient(); + const [presence, setPresence] = useState([]); + const [messages, setMessages] = useState([]); + + useEffect(() => { + if (!client || !channelId) { + setPresence([]); + setMessages([]); + return; + } + + client.subscribe(channelId); + + const onSubscribed = (msg: { presence?: string[] }) => { + setPresence(msg.presence ?? []); + }; + + const onJoin = (msg: { humanId?: string }) => { + if (msg.humanId) { + setPresence((prev) => + prev.includes(msg.humanId!) ? prev : [...prev, msg.humanId!], + ); + } + }; + + const onLeave = (msg: { humanId?: string }) => { + if (msg.humanId) { + setPresence((prev) => prev.filter((id) => id !== msg.humanId)); + } + }; + + const onMessage = (msg: { humanId?: string; payload?: unknown }) => { + if (msg.humanId) { + setMessages((prev) => [ + ...prev, + { humanId: msg.humanId!, payload: msg.payload }, + ]); + } + }; + + client.on(channelId, "subscribed", onSubscribed); + client.on(channelId, "join", onJoin); + client.on(channelId, "leave", onLeave); + client.on(channelId, "message", onMessage); + + return () => { + client.off(channelId, "subscribed", onSubscribed); + client.off(channelId, "join", onJoin); + client.off(channelId, "leave", onLeave); + client.off(channelId, "message", onMessage); + client.unsubscribe(channelId); + }; + }, [client, channelId]); + + const sendMessage = useCallback( + (payload: unknown) => { + if (client && channelId) { + client.sendMessage(channelId, payload); + } + }, + [client, channelId], + ); + + return { presence, messages, sendMessage }; +} diff --git a/js/mobile/src/hooks/use-event.ts b/js/mobile/src/hooks/use-event.ts new file mode 100644 index 0000000..a3ae51f --- /dev/null +++ b/js/mobile/src/hooks/use-event.ts @@ -0,0 +1,18 @@ +import { useCallback, useLayoutEffect, useRef } from "react"; + +// Polyfill for React's `useEffectEvent` (canary). The returned function has a +// stable identity but always sees the latest closure — exactly what +// `useEffectEvent` provides. Stable enough that we use it everywhere we'd +// otherwise reach for a ref + .current dance inside an effect. +// +// Replace with `useEffectEvent` once it ships in stable React. Call sites +// don't need to change. +export function useEvent( + fn: (...args: TArgs) => TReturn, +): (...args: TArgs) => TReturn { + const ref = useRef(fn); + useLayoutEffect(() => { + ref.current = fn; + }); + return useCallback((...args: TArgs) => ref.current(...args), []); +} diff --git a/js/mobile/src/hooks/use-networks.ts b/js/mobile/src/hooks/use-networks.ts new file mode 100644 index 0000000..1ea1f38 --- /dev/null +++ b/js/mobile/src/hooks/use-networks.ts @@ -0,0 +1,23 @@ +import { useQuery } from "@tanstack/react-query"; +import { apiClient } from "@/api/client"; +import { useAuthStore } from "@/stores/auth-store"; + +export function useNetworks() { + return useQuery({ + queryKey: ["networks"], + queryFn: () => apiClient.listNetworks(), + meta: { toastOnError: true }, + }); +} + +export function useNetwork(networkId: string) { + const { data: networks } = useNetworks(); + return networks?.find((n) => n.id === networkId) ?? null; +} + +export function useIsNetworkAdmin(networkId: string): boolean { + const network = useNetwork(networkId); + const userId = useAuthStore((s) => s.user?.id); + if (!network || !userId) return false; + return network.admin_human.id === userId; +} diff --git a/js/mobile/src/hooks/use-particle.ts b/js/mobile/src/hooks/use-particle.ts new file mode 100644 index 0000000..f66c91d --- /dev/null +++ b/js/mobile/src/hooks/use-particle.ts @@ -0,0 +1,186 @@ +import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import type { QueryFieldFilterConstraint } from "firebase/firestore"; +import { + subscribeToParticle, + subscribeToParticleChildren, + subscribeToLatestChild, + getParticle, + getParticleChildren, +} from "@/lib/firestore-particles"; +import type { Particle } from "@/api/types"; +import { + type ParticlePath, + toFirestoreDocPath, + toFirestoreChildrenPath, +} from "@/lib/particle-path"; +import { logError } from "@/lib/errors"; + +interface UseLiveParticleResult { + particle: Particle | null; + isLoading: boolean; + error: Error | null; +} + +export function useLiveParticle(path: ParticlePath): UseLiveParticleResult { + const [particle, setParticle] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + setIsLoading(true); + setError(null); + setParticle(null); + + const docPath = toFirestoreDocPath(path); + const unsubscribe = subscribeToParticle( + docPath, + (data) => { + setParticle(data); + setIsLoading(false); + }, + (err) => { + setError(err); + setIsLoading(false); + }, + ); + + return unsubscribe; + }, [path]); + + return { particle, isLoading, error }; +} + +interface UseLiveParticleChildrenResult { + children: Particle[]; + isLoading: boolean; + error: Error | null; +} + +interface UseLiveParticleChildrenParams { + orderByField?: string; + orderDirection?: "asc" | "desc"; + visibilityScopes?: string[]; + onAdded?: (child: Particle) => void; + onRemoved?: (child: Particle, updatedChildren: Particle[]) => void; + whereFilter?: QueryFieldFilterConstraint; + /** Optional cap on results. Changes trigger a re-subscription. */ + limit?: number; +} + +export function useLiveParticleChildren( + path: ParticlePath | undefined, + { + orderByField = "created_at", + orderDirection = "desc", + visibilityScopes, + onAdded, + onRemoved, + whereFilter, + limit, + }: UseLiveParticleChildrenParams = {}, +): UseLiveParticleChildrenResult { + const [children, setChildren] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!path) { + setChildren([]); + setIsLoading(false); + return; + } + + setIsLoading(true); + setError(null); + setChildren([]); + + const collectionPath = toFirestoreChildrenPath(path); + + const unsubscribe = subscribeToParticleChildren(collectionPath, { + onData: (data) => { + setChildren(data); + setIsLoading(false); + }, + onError: (err) => { + logError(err, { scope: "firestore.particle-children", path }); + setError(err); + setIsLoading(false); + }, + visibilityScopes, + orderByField, + orderDirection, + onAdded, + onRemoved, + whereFilter, + limit, + }); + + return unsubscribe; + // The hook intentionally keys only on path/whereFilter/limit — desktop + // does the same. Visibility scope changes are absorbed by the active + // listener; reordering causes a re-subscription. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [path, whereFilter, limit]); + + return { children, isLoading, error }; +} + +interface UseLiveLatestChildResult { + latestChild: Particle | null; + isLoading: boolean; +} + +export function useLiveLatestChild( + path: ParticlePath, +): UseLiveLatestChildResult { + const [latestChild, setLatestChild] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + setIsLoading(true); + setLatestChild(null); + + const unsubscribe = subscribeToLatestChild( + toFirestoreChildrenPath(path), + (data) => { + setLatestChild(data); + setIsLoading(false); + }, + (err) => { + logError(err, { scope: "firestore.latest-child", path }); + setIsLoading(false); + }, + ); + + return unsubscribe; + }, [path]); + + return { latestChild, isLoading }; +} + +export function useParticle(path?: ParticlePath) { + return useQuery({ + queryKey: ["particle", path], + queryFn: async () => { + if (!path) return null; + const docPath = toFirestoreDocPath(path); + const particle = await getParticle(docPath); + return particle; + }, + enabled: !!path, + }); +} + +export function useParticleChildren(path?: ParticlePath) { + return useQuery({ + queryKey: ["particle-children", path], + queryFn: async () => { + if (!path) return []; + const collectionPath = toFirestoreChildrenPath(path); + return getParticleChildren(collectionPath); + }, + enabled: !!path, + staleTime: 1000 * 60 * 5, // 5 min — attachments don't change + }); +} diff --git a/js/mobile/src/hooks/use-stream-particles.ts b/js/mobile/src/hooks/use-stream-particles.ts new file mode 100644 index 0000000..7085197 --- /dev/null +++ b/js/mobile/src/hooks/use-stream-particles.ts @@ -0,0 +1,95 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { where, type QueryFieldFilterConstraint } from "firebase/firestore"; +import { useLiveParticleChildren } from "@/hooks/use-particle"; +import { useAuthStore } from "@/stores/auth-store"; +import { parseParticlePath, type ParticlePath } from "@/lib/particle-path"; +import type { Particle, StreamProperties } from "@/api/types"; + +export type StreamParticle = Particle & { + type: "stream"; + properties: StreamProperties; +}; + +const CLOSED_INITIAL_PAGE_SIZE = 50; +const CLOSED_PAGE_INCREMENT = 50; + +// Stable where-constraint references so the Firestore subscription only +// re-attaches when the tab actually changes, not on every render. +const OPEN_STATUS_FILTER = where("status", "==", "open"); +const CLOSED_STATUS_FILTER = where("status", "==", "closed"); + +function useVisibilityScopes(userId?: string, networkId?: string) { + return useMemo(() => { + const scopes: string[] = []; + if (userId) scopes.push(`human:${userId}`); + if (networkId) scopes.push(`network:${networkId}`); + return scopes; + }, [userId, networkId]); +} + +interface UseStreamParticlesOptions { + /** + * Which streams to subscribe to. Open streams are loaded in full (bounded + * by active work — full realtime coverage is needed for autoplay/huddles). + * Closed streams are paginated via `loadMore`. + */ + status: "open" | "closed"; +} + +interface UseStreamParticlesResult { + streams: StreamParticle[]; + isLoading: boolean; + error: Error | null; + networkId: string; + /** True when more closed streams may exist beyond the current window. */ + canLoadMore: boolean; + /** Extend the pagination window. No-op on the open tab. */ + loadMore: () => void; +} + +export function useStreamParticles( + path: ParticlePath, + { status }: UseStreamParticlesOptions, +): UseStreamParticlesResult { + const { networkId } = parseParticlePath(path); + const user = useAuthStore((s) => s.user); + const visibilityScopes = useVisibilityScopes(user?.id, networkId); + + const [closedLimit, setClosedLimit] = useState(CLOSED_INITIAL_PAGE_SIZE); + + // Every time the user switches back to the closed tab, start with a fresh + // window. Avoids an ever-growing subscription across a long session. + useEffect(() => { + if (status === "closed") { + setClosedLimit(CLOSED_INITIAL_PAGE_SIZE); + } + }, [status]); + + const whereFilter: QueryFieldFilterConstraint = + status === "open" ? OPEN_STATUS_FILTER : CLOSED_STATUS_FILTER; + const limit = status === "closed" ? closedLimit : undefined; + + const { children, isLoading, error } = useLiveParticleChildren(path, { + orderByField: "last_child_created_at", + orderDirection: "desc", + visibilityScopes, + whereFilter, + limit, + }); + + const streams = useMemo( + () => children.filter((c): c is StreamParticle => c.type === "stream"), + [children], + ); + + // Heuristic: if we got back as many items as we asked for, assume there + // might be more. Clicking load-more when there are no more is a no-op. + const canLoadMore = status === "closed" && streams.length >= closedLimit; + + const loadMore = useCallback(() => { + if (status !== "closed") return; + setClosedLimit((prev) => prev + CLOSED_PAGE_INCREMENT); + }, [status]); + + return { streams, isLoading, error, networkId, canLoadMore, loadMore }; +} diff --git a/js/mobile/src/hooks/use-stream-playback.ts b/js/mobile/src/hooks/use-stream-playback.ts new file mode 100644 index 0000000..5bfe03b --- /dev/null +++ b/js/mobile/src/hooks/use-stream-playback.ts @@ -0,0 +1,258 @@ +import { useCallback, useEffect, useMemo, useReducer, useRef } from "react"; +import { useAuthStore } from "@/stores/auth-store"; +import type { Particle } from "@/api/types"; +import { useLiveParticleChildren } from "@/hooks/use-particle"; +import { toFirestoreDocPath, type ParticlePath } from "@/lib/particle-path"; +import { updateStreamPlaybackMarker } from "@/lib/firestore-particles"; +import { logError } from "@/lib/errors"; +import { useEvent } from "@/hooks/use-event"; + +// --- Playback reducer (ID-based) --- + +type PlaybackStatus = "idle" | "playing" | "ended"; + +interface PlaybackState { + currentParticleId: string | null; + status: PlaybackStatus; + initialized: boolean; +} + +type PlaybackAction = + | { type: "INIT"; particleId: string } + | { type: "SET_PARTICLE"; particleId: string } + | { type: "END" } + | { type: "PARTICLE_ADDED"; particleId: string } + | { + type: "PARTICLE_REMOVED"; + removedParticleId: string; + fallbackParticleId: string | null; + }; + +const initialState: PlaybackState = { + currentParticleId: null, + status: "idle", + initialized: false, +}; + +function playbackReducer( + state: PlaybackState, + action: PlaybackAction, +): PlaybackState { + switch (action.type) { + case "INIT": + return { + currentParticleId: action.particleId, + status: "playing", + initialized: true, + }; + case "SET_PARTICLE": + return { + ...state, + currentParticleId: action.particleId, + status: "playing", + }; + case "END": + return { ...state, status: "ended" }; + case "PARTICLE_ADDED": + if (state.status === "ended") { + return { + ...state, + currentParticleId: action.particleId, + status: "playing", + }; + } + return state; + case "PARTICLE_REMOVED": + if (action.removedParticleId !== state.currentParticleId) return state; + if (action.fallbackParticleId) { + return { + ...state, + currentParticleId: action.fallbackParticleId, + status: "playing", + }; + } + return { ...state, currentParticleId: null, status: "idle" }; + } +} + +const INIT_FALLBACK_TIMEOUT_MS = 5000; + +interface UseStreamPlaybackResult { + children: Particle[]; + currentParticle: Particle | null; + currentIndex: number; + status: PlaybackStatus; + initialized: boolean; + next: () => void; + prev: () => void; + goTo: (index: number) => void; + goToParticle: (particleId: string) => void; +} + +export function useStreamPlayback( + streamParticle: Particle & { type: "stream" }, + path: ParticlePath, +): UseStreamPlaybackResult { + const userId = useAuthStore((s) => s.user?.id); + const [state, dispatch] = useReducer(playbackReducer, initialState); + // Track which stream we initialized for, so navigating to a sibling resets cleanly. + const initializedForRef = useRef(null); + + const onParticleAdded = useCallback((particle: Particle) => { + dispatch({ type: "PARTICLE_ADDED", particleId: particle.id }); + }, []); + + const onParticleRemoved = useEvent( + (removed: Particle, updatedChildren: Particle[]) => { + const fallbackIndex = Math.min(currentIndex, updatedChildren.length - 1); + const fallback = updatedChildren[Math.max(0, fallbackIndex)]; + dispatch({ + type: "PARTICLE_REMOVED", + removedParticleId: removed.id, + fallbackParticleId: fallback?.id ?? null, + }); + }, + ); + + const { children } = useLiveParticleChildren(path, { + orderByField: "created_at", + orderDirection: "asc", + onAdded: onParticleAdded, + onRemoved: onParticleRemoved, + }); + + // Derive current index and particle from ID + const currentIndex = useMemo(() => { + if (!state.currentParticleId) return -1; + return children.findIndex((c) => c.id === state.currentParticleId); + }, [children, state.currentParticleId]); + + const currentParticle = currentIndex !== -1 ? children[currentIndex] : null; + + const initFallback = useEvent(() => { + if (state.initialized || children.length === 0) return; + initializedForRef.current = streamParticle.id; + dispatch({ type: "INIT", particleId: children[0].id }); + }); + + // --- Init logic: runs on every children change until initialized --- + useEffect(() => { + if ( + initializedForRef.current !== null && + initializedForRef.current !== streamParticle.id + ) { + initializedForRef.current = null; + } + + if (state.initialized && initializedForRef.current === streamParticle.id) + return; + + if (children.length === 0) return; + + const playbackPosition = streamParticle.playback_markers?.[userId ?? ""]; + + if (!playbackPosition) { + initializedForRef.current = streamParticle.id; + dispatch({ type: "INIT", particleId: children[0].id }); + return; + } + + const found = children.find( + (c) => c.created_at.getTime() > playbackPosition.getTime(), + ); + + if (found) { + initializedForRef.current = streamParticle.id; + dispatch({ type: "INIT", particleId: found.id }); + return; + } else { + initializedForRef.current = streamParticle.id; + dispatch({ + type: "INIT", + particleId: children[children.length - 1].id, + }); + } + + const timeout = setTimeout(initFallback, INIT_FALLBACK_TIMEOUT_MS); + return () => clearTimeout(timeout); + }, [ + children, + streamParticle.id, + streamParticle.playback_markers, + userId, + state.initialized, + initFallback, + ]); + + // --- Persist playback marker (only advance forward, never backwards) --- + const lastPersistedMarkerRef = useRef(null); + + useEffect(() => { + if (!userId || !state.initialized || !currentParticle) return; + + const currentTime = currentParticle.created_at; + const existingMarker = + lastPersistedMarkerRef.current ?? + streamParticle.playback_markers?.[userId]; + + if (existingMarker && currentTime.getTime() <= existingMarker.getTime()) + return; + + lastPersistedMarkerRef.current = currentTime; + const streamDocPath = toFirestoreDocPath(path); + updateStreamPlaybackMarker(streamDocPath, userId, currentTime).catch( + (err) => logError(err, { scope: "playback.marker", path }), + ); + // streamParticle.playback_markers is read at effect time; not in deps to + // avoid double-writes when the snapshot we just persisted echoes back. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentParticle?.id, state.initialized, userId, path]); + + // --- Navigation callbacks --- + const next = useCallback(() => { + if (currentIndex === -1) return; + if (currentIndex < children.length - 1) { + dispatch({ + type: "SET_PARTICLE", + particleId: children[currentIndex + 1].id, + }); + } else { + dispatch({ type: "END" }); + } + }, [children, currentIndex]); + + const prev = useCallback(() => { + if (currentIndex <= 0) return; + dispatch({ + type: "SET_PARTICLE", + particleId: children[currentIndex - 1].id, + }); + }, [children, currentIndex]); + + const goTo = useCallback( + (index: number) => { + if (index >= 0 && index < children.length) { + dispatch({ type: "SET_PARTICLE", particleId: children[index].id }); + } + }, + [children], + ); + + // If the particle isn't in `children` yet (e.g. just-created), the live + // query will resolve it shortly and the derived index/particle will catch up. + const goToParticle = useCallback((particleId: string) => { + dispatch({ type: "SET_PARTICLE", particleId }); + }, []); + + return { + children, + currentParticle, + currentIndex, + status: state.status, + initialized: state.initialized, + next, + prev, + goTo, + goToParticle, + }; +} diff --git a/js/mobile/src/hooks/use-suspend-playback.ts b/js/mobile/src/hooks/use-suspend-playback.ts new file mode 100644 index 0000000..8ca363f --- /dev/null +++ b/js/mobile/src/hooks/use-suspend-playback.ts @@ -0,0 +1,17 @@ +import { useEffect, useId } from "react"; +import { usePlaybackPauseStore } from "@/stores/playback-pause-store"; + +/** + * Suspend stream playback while `active` is true. The hook owns its own + * registration id; multiple instances compose. `label` is for devtools only. + */ +export function useSuspendPlayback(active: boolean, label: string) { + const id = useId(); + + useEffect(() => { + if (!active) return; + const { add, remove } = usePlaybackPauseStore.getState(); + add(id, label); + return () => remove(id); + }, [active, id, label]); +} diff --git a/js/mobile/src/lib/errors.ts b/js/mobile/src/lib/errors.ts new file mode 100644 index 0000000..d3ac284 --- /dev/null +++ b/js/mobile/src/lib/errors.ts @@ -0,0 +1,90 @@ +import { z } from "zod"; +import { appEnv } from "@/config/env"; + +export class ApiError extends Error { + constructor( + public status: number, + message: string, + ) { + super(message); + this.name = "ApiError"; + } +} + +/** + * Thrown when a free-plan network attempts to create a non-container particle + * after hitting its daily message limit. Compose UI also disables triggers + * proactively via `useNetworkUsage` — this throw is a last-line defense. + */ +export class QuotaExceededError extends Error { + constructor(public readonly networkId: string) { + super("Daily message limit reached"); + this.name = "QuotaExceededError"; + } +} + +function normalizeMessage(message: string): string { + return message.replace(/^Error:\s*/, "").trim(); +} + +export function toUserMessage(err: unknown): string { + if (err instanceof ApiError) { + if (err.status === 401) return "Please sign in again."; + if (err.status === 403) return "You don't have permission to do that."; + if (err.status === 404) return "Not found."; + if (err.status === 408 || err.status === 429) { + return "Please try again in a moment."; + } + if (err.status >= 500) { + return "Something went wrong on our end. Please try again."; + } + return normalizeMessage(err.message) || "Request failed."; + } + + if (err instanceof z.ZodError) { + return "Received unexpected data from the server."; + } + + if (err instanceof TypeError && /fetch|network/i.test(err.message)) { + return "Network error. Check your connection."; + } + + if (err instanceof Error) { + return normalizeMessage(err.message) || "Something went wrong."; + } + + return "Something went wrong."; +} + +type ErrorContext = Record; +type ErrorSink = (err: unknown, context?: ErrorContext) => void; + +// Sentry (or any observability backend) installs itself via `installErrorSinks` +// from App.tsx. Until then, logError is a dev-only console call and reportError +// always prints — no call site needs to know. +let captureSink: ErrorSink | null = null; +let breadcrumbSink: ErrorSink | null = null; + +export function installErrorSinks(sinks: { + capture: ErrorSink; + breadcrumb: ErrorSink; +}): void { + captureSink = sinks.capture; + breadcrumbSink = sinks.breadcrumb; +} + +/** Expected-but-recordable failures. Breadcrumb only — never pages anyone. */ +export function logError(err: unknown, context?: ErrorContext): void { + if (appEnv === "dev") { + // eslint-disable-next-line no-console + console.error("[error]", err, context ?? {}); + } + breadcrumbSink?.(err, context); +} + +/** Unexpected failures the user may not see. Always captured. */ +export function reportError(err: unknown, context?: ErrorContext): void { + // eslint-disable-next-line no-console + console.error("[error]", err, context ?? {}); + captureSink?.(err, context); +} diff --git a/js/mobile/src/lib/firestore-particles.ts b/js/mobile/src/lib/firestore-particles.ts new file mode 100644 index 0000000..ce1d19e --- /dev/null +++ b/js/mobile/src/lib/firestore-particles.ts @@ -0,0 +1,435 @@ +import { + collection, + doc, + onSnapshot, + addDoc, + getDoc, + getDocs, + updateDoc, + query, + orderBy, + limit, + serverTimestamp, + where, + Timestamp, + arrayUnion, + arrayRemove, + type DocumentData, + type FirestoreDataConverter, + type QueryDocumentSnapshot, + type SnapshotOptions, + type Unsubscribe, + type QueryFieldFilterConstraint, +} from "firebase/firestore"; +import { firestoreDb } from "@/firebase"; +import { isContainerType, ParticleSchema } from "@/api/types"; +import type { + Particle, + ParticleType, + ParticlePropertiesMap, + Reactions, +} from "@/api/types"; + +// --- Converter --- + +const particleConverter: FirestoreDataConverter = { + toFirestore(particle: Particle): DocumentData { + const { id: _id, created_at, updated_at, ...rest } = particle; + const deletedAt = + "deleted_at" in particle ? particle.deleted_at : undefined; + return { + ...rest, + created_at: Timestamp.fromDate(created_at), + ...(updated_at && { updated_at: Timestamp.fromDate(updated_at) }), + ...(deletedAt && { deleted_at: Timestamp.fromDate(deletedAt) }), + }; + }, + fromFirestore( + snap: QueryDocumentSnapshot, + options?: SnapshotOptions, + ): Particle { + const raw = snap.data(options); + if (typeof raw.type !== "string") { + throw new Error(`Invalid particle type: ${raw.type}`); + } + const type = raw.type as ParticleType; + switch (type) { + case "stream": + return ParticleSchema.parse({ + id: snap.id, + type: raw.type, + properties: raw.properties, + created_at: (raw.created_at as Timestamp).toDate(), + created_by_human_id: raw.created_by_human_id, + updated_at: raw.updated_at + ? (raw.updated_at as Timestamp).toDate() + : undefined, + visible_to: raw.visible_to, + playback_markers: raw.playback_markers + ? Object.fromEntries( + Object.entries(raw.playback_markers).map(([key, value]) => [ + key, + (value as Timestamp).toDate(), + ]), + ) + : undefined, + last_child_created_at: raw.last_child_created_at + ? (raw.last_child_created_at as Timestamp).toDate() + : undefined, + huddle_active_participants: + raw.huddle_active_participants ?? undefined, + status: raw.status ?? undefined, + }); + case "folder": + return ParticleSchema.parse({ + id: snap.id, + type: raw.type, + properties: raw.properties, + created_at: (raw.created_at as Timestamp).toDate(), + created_by_human_id: raw.created_by_human_id, + updated_at: raw.updated_at + ? (raw.updated_at as Timestamp).toDate() + : undefined, + visible_to: raw.visible_to, + }); + case "media": + case "file": + case "text": + case "quest": + case "paper": { + // Firestore stores timestamps as `Timestamp`; zod expects `Date`. Text + // particles carry `properties.edited_at`, so coerce it if present. + const properties = + type === "text" && raw.properties?.edited_at + ? { + ...raw.properties, + edited_at: (raw.properties.edited_at as Timestamp).toDate(), + } + : raw.properties; + return ParticleSchema.parse({ + id: snap.id, + type: raw.type, + properties, + created_at: (raw.created_at as Timestamp).toDate(), + created_by_human_id: raw.created_by_human_id, + updated_at: raw.updated_at + ? (raw.updated_at as Timestamp).toDate() + : undefined, + reactions: raw.reactions ?? undefined, + deleted_at: raw.deleted_at + ? (raw.deleted_at as Timestamp).toDate() + : undefined, + deleted_by_human_id: raw.deleted_by_human_id ?? undefined, + }); + } + default: + throw new Error(`Unknown particle type: ${type}`); + } + }, +}; + +// --- Typed reference helpers --- + +function typedDoc(path: string) { + return doc(firestoreDb, path).withConverter(particleConverter); +} + +function typedCollection(path: string) { + return collection(firestoreDb, path).withConverter(particleConverter); +} + +// --- Exported operations --- + +export function subscribeToParticle( + docPath: string, + onData: (particle: Particle | null) => void, + onError: (error: Error) => void, +): Unsubscribe { + return onSnapshot( + typedDoc(docPath), + (snap) => { + onData(snap.exists() ? snap.data() : null); + }, + onError, + ); +} + +export async function getParticle(docPath: string): Promise { + const docSnap = await getDoc(typedDoc(docPath)); + if (!docSnap.exists()) { + return null; + } + return docSnap.data(); +} + +export interface GetParticleChildrenOptions { + orderByField: string; + orderDirection: "asc" | "desc"; +} + +export async function getParticleChildren( + collectionPath: string, + { + orderByField = "created_at", + orderDirection = "asc", + }: GetParticleChildrenOptions = { + orderByField: "created_at", + orderDirection: "asc", + }, +): Promise { + const q = query( + typedCollection(collectionPath), + orderBy(orderByField, orderDirection), + ); + const snap = await getDocs(q); + return snap.docs.map((d) => d.data()); +} + +export interface SubscribeToParticleChildrenOptions { + onData: (children: Particle[]) => void; + onError: (error: Error) => void; + visibilityScopes?: string[]; + orderByField?: string; + orderDirection?: "asc" | "desc"; + onAdded?: (child: Particle) => void; + onRemoved?: (child: Particle, updatedChildren: Particle[]) => void; + whereFilter?: QueryFieldFilterConstraint; + /** Optional cap on results. Applied after order/where constraints. */ + limit?: number; +} + +export function subscribeToParticleChildren( + collectionPath: string, + { + onData, + onError, + visibilityScopes = [], + orderByField = "created_at", + orderDirection = "desc", + onAdded, + onRemoved, + whereFilter, + limit: limitValue, + }: SubscribeToParticleChildrenOptions, +): Unsubscribe { + let q = query( + typedCollection(collectionPath), + orderBy(orderByField, orderDirection), + ); + if (visibilityScopes.length > 0) { + q = query(q, where("visible_to", "array-contains-any", visibilityScopes)); + } + if (whereFilter) { + q = query(q, whereFilter); + } + if (limitValue !== undefined) { + q = query(q, limit(limitValue)); + } + return onSnapshot( + q, + (snap) => { + const updatedChildren = snap.docs.map((d) => d.data()); + onData(updatedChildren); + + if (onAdded || onRemoved) { + for (const change of snap.docChanges()) { + if (change.type === "added" && onAdded) onAdded(change.doc.data()); + if (change.type === "removed" && onRemoved) + onRemoved(change.doc.data(), updatedChildren); + } + } + }, + onError, + ); +} + +export function subscribeToLatestChild( + collectionPath: string, + onData: (child: Particle | null) => void, + onError: (error: Error) => void, +): Unsubscribe { + const q = query( + typedCollection(collectionPath), + orderBy("created_at", "desc"), + limit(1), + ); + return onSnapshot( + q, + (snap) => { + onData(snap.empty ? null : snap.docs[0].data()); + }, + onError, + ); +} + +// This creates a new particle document with the given properties and returns its ID. +export async function createParticle( + collectionPath: string, + type: T, + properties: ParticlePropertiesMap[T], + createdByHumanId: string, + // Must be passed for container types + visibleTo?: string[], +): Promise { + if (isContainerType(type) && (!visibleTo || visibleTo.length === 0)) { + throw new Error( + `visibleTo is required for container type ${type} and cannot be empty`, + ); + } + + const particle: Particle = ParticleSchema.parse({ + id: "", // ignored by toFirestore, but needed to satisfy the type + type, + properties, + created_at: new Date(), + created_by_human_id: createdByHumanId, + ...(visibleTo ? { visible_to: visibleTo } : {}), + }); + const ref = await addDoc(typedCollection(collectionPath), particle); + return ref.id; +} + +export async function createStreamParticle( + collectionPath: string, + properties: ParticlePropertiesMap["stream"], + createdByHumanId: string, + visibleTo?: string[], +): Promise { + if (!visibleTo || visibleTo.length === 0) { + throw new Error("visibleTo is required for streams and cannot be empty"); + } + + const particle: Particle = ParticleSchema.parse({ + id: "", + type: "stream", + properties, + created_at: new Date(), + created_by_human_id: createdByHumanId, + visible_to: visibleTo, + status: "open", + }); + const ref = await addDoc(typedCollection(collectionPath), particle); + return ref.id; +} + +// This allows updating properties without overwriting the entire properties object +export async function updateParticleProperties( + docPath: string, + properties: Partial, +): Promise { + const particleRef = typedDoc(docPath); + // Take the partial and create a new object with dot notation + // e.g. { title: "New Title" } becomes { "properties.title": "New Title" } + const updatedProperties: Record = {}; + for (const key in properties) { + updatedProperties[`properties.${key}`] = properties[key]; + } + await updateDoc(particleRef, { + ...updatedProperties, + updated_at: serverTimestamp(), + }); +} + +// Edits the body of a text particle and stamps `properties.edited_at` so +// readers can see that the message was edited (distinct from `updated_at`, +// which is bumped by any write — visibility, reactions, etc.). +export async function editTextParticleContent( + docPath: string, + content: string, +): Promise { + const particleRef = typedDoc(docPath); + await updateDoc(particleRef, { + "properties.content": content, + "properties.edited_at": serverTimestamp(), + updated_at: serverTimestamp(), + }); +} + +export async function updateParticleVisibleTo( + docPath: string, + visibleTo: string[], +): Promise { + const particleRef = typedDoc(docPath); + const particle = await getParticle(docPath); + if (!particle) { + throw new Error(`Particle not found at path: ${docPath}`); + } + if (!isContainerType(particle.type)) { + throw new Error( + `Only container particles can have visible_to field. Particle at ${docPath} is of type ${particle.type}`, + ); + } + + await updateDoc(particleRef, { + visible_to: visibleTo, + updated_at: serverTimestamp(), + }); +} + +// CAUTION: use the other type safe update functions in most cases +// There is no checking whether this field actually exists on the particle type, so it can lead to inconsistent data if used incorrectly +export async function updateParticle( + docPath: string, + fieldName: string, + value: unknown, +): Promise { + const particleRef = typedDoc(docPath); + await updateDoc(particleRef, { + [fieldName]: value, + updated_at: serverTimestamp(), + }); +} + +export async function updateStreamStatus( + docPath: string, + status: "open" | "closed", +): Promise { + const particleRef = typedDoc(docPath); + await updateDoc(particleRef, { status, updated_at: serverTimestamp() }); +} + +/** + * Soft-delete (tombstone) a non-container particle. The Firestore doc stays + * in place so concurrent viewers see the deletion inline rather than being + * bumped to an adjacent particle. Idempotent — re-calling on an already + * tombstoned doc just refreshes the timestamp. + */ +export async function softDeleteParticle( + docPath: string, + humanId: string, +): Promise { + const particleRef = typedDoc(docPath); + await updateDoc(particleRef, { + deleted_at: serverTimestamp(), + deleted_by_human_id: humanId, + updated_at: serverTimestamp(), + }); +} + +export async function updateStreamPlaybackMarker( + docPath: string, + humanId: string, + playbackPositionAt: Date, +): Promise { + const particleRef = typedDoc(docPath); + const markerField = `playback_markers.${humanId}`; + await updateDoc(particleRef, { + [markerField]: Timestamp.fromDate(playbackPositionAt), + updated_at: serverTimestamp(), + }); +} + +export async function toggleParticleReaction( + docPath: string, + emoji: string, + humanId: string, + currentReactions?: Reactions, +): Promise { + const particleRef = typedDoc(docPath); + const field = `reactions.${emoji}`; + const alreadyReacted = currentReactions?.[emoji]?.includes(humanId) ?? false; + await updateDoc(particleRef, { + [field]: alreadyReacted ? arrayRemove(humanId) : arrayUnion(humanId), + updated_at: serverTimestamp(), + }); +} diff --git a/js/mobile/src/lib/humans.ts b/js/mobile/src/lib/humans.ts new file mode 100644 index 0000000..543fdb6 --- /dev/null +++ b/js/mobile/src/lib/humans.ts @@ -0,0 +1,43 @@ +import type { Human } from "@/api/types"; +import { getInitials } from "@/lib/utils"; + +export const REMOVED_MEMBER_LABEL = "Removed member"; +export const REMOVED_MEMBER_INITIALS = "–"; + +export interface HumanDisplay { + /** True when the human was found in the provided list. */ + exists: boolean; + /** Short name for inline text (e.g. message sender). */ + displayName: string; + /** Full email or fallback label for tooltips. */ + email: string; + /** Initials for avatar fallback. */ + initials: string; +} + +/** + * Resolve a human's display info by id, falling back consistently when the + * human has been removed from the network. Member content (particles, reactions, + * etc.) is retained after removal, so every render path needs a graceful fallback + * instead of leaking raw ids into the UI. + */ +export function resolveHumanDisplay( + humanId: string | null | undefined, + humans: Human[] | undefined, +): HumanDisplay { + const human = humanId ? humans?.find((h) => h.id === humanId) : undefined; + if (!human) { + return { + exists: false, + displayName: REMOVED_MEMBER_LABEL, + email: REMOVED_MEMBER_LABEL, + initials: REMOVED_MEMBER_INITIALS, + }; + } + return { + exists: true, + displayName: human.email_prefix, + email: human.email, + initials: getInitials(human.email), + }; +} diff --git a/js/mobile/src/lib/particle-path.ts b/js/mobile/src/lib/particle-path.ts new file mode 100644 index 0000000..905e1fd --- /dev/null +++ b/js/mobile/src/lib/particle-path.ts @@ -0,0 +1,70 @@ +/** + * ParticlePath is a branded string type representing a URL-style path + * to a particle in the hierarchy: /{networkId}/{segment1}/{segment2}/... + * + * Using a branded type prevents accidentally passing raw strings where + * a validated particle path is expected. + */ +declare const __brand: unique symbol; +export type ParticlePath = string & { readonly [__brand]: true }; + +/** + * Construct a ParticlePath from a network ID and optional particle segments. + * + * @example + * particlePath("net1", []) // => "/net1" + * particlePath("net1", ["p1"]) // => "/net1/p1" + * particlePath("net1", ["p1","p2"])// => "/net1/p1/p2" + */ +export function particlePath( + networkId: string, + segments: string[] = [], +): ParticlePath { + return `/${[networkId, ...segments].join("/")}` as ParticlePath; +} + +/** + * Parse a ParticlePath back into its network ID and particle segments. + */ +export function parseParticlePath(path: ParticlePath): { + networkId: string; + segments: string[]; +} { + const parts = path.split("/").filter(Boolean); + return { networkId: parts[0], segments: parts.slice(1) }; +} + +/** + * Convert a ParticlePath to the Firestore document path for that particle. + * + * Firestore structure: + * /net1 → networks/net1/children (collection) + * /net1/p1 → networks/net1/children/p1 (document) + * /net1/p1/p2 → networks/net1/children/p1/children/p2 (document) + */ +export function toFirestoreDocPath(path: ParticlePath): string { + const { networkId, segments } = parseParticlePath(path); + const base = `networks/${networkId}/children`; + if (segments.length === 0) return base; + + const parts: string[] = [base, segments[0]]; + for (let i = 1; i < segments.length; i++) { + parts.push("children", segments[i]); + } + return parts.join("/"); +} + +/** + * Convert a ParticlePath to the Firestore collection path for its children. + * + * /net1 → networks/net1/children (root particles) + * /net1/p1 → networks/net1/children/p1/children + * /net1/p1/p2 → networks/net1/children/p1/children/p2/children + */ +export function toFirestoreChildrenPath(path: ParticlePath): string { + const { segments } = parseParticlePath(path); + if (segments.length === 0) { + return toFirestoreDocPath(path); + } + return `${toFirestoreDocPath(path)}/children`; +} diff --git a/js/mobile/src/lib/pusher-client.ts b/js/mobile/src/lib/pusher-client.ts new file mode 100644 index 0000000..99ce04a --- /dev/null +++ b/js/mobile/src/lib/pusher-client.ts @@ -0,0 +1,286 @@ +/** + * PusherClient manages a WebSocket connection to the pusher service. + * Handles authentication, reconnection with exponential backoff, channel + * subscriptions, and event dispatching. + * + * Identical behavior to the desktop client (js/desktop/src/lib/pusher-client.ts). + * React Native ships a WebSocket polyfill, so this code runs unchanged. + */ + +import { logError, reportError } from "@/lib/errors"; + +export type ConnectionState = + | "disconnected" + | "connecting" + | "connected" + | "reconnecting"; + +export interface ChannelMessage { + humanId: string; + payload: unknown; +} + +interface ServerMessage { + type: "subscribed" | "join" | "leave" | "message" | "error"; + channel?: string; + humanId?: string; + presence?: string[]; + payload?: unknown; + message?: string; +} + +type ChannelEventType = "subscribed" | "join" | "leave" | "message"; +type ChannelEventCallback = (msg: ServerMessage) => void; + +interface PusherClientConfig { + url: string; + getToken: () => string | null; +} + +const INITIAL_RECONNECT_DELAY = 1000; +const MAX_RECONNECT_DELAY = 30000; +const PING_INTERVAL = 20000; // 20s — keeps alive through GKE gateway timeout + +export class PusherClient { + private config: PusherClientConfig; + private ws: WebSocket | null = null; + private state: ConnectionState = "disconnected"; + private stateListeners = new Set<(state: ConnectionState) => void>(); + + private listeners = new Map< + string, + Map> + >(); + + private activeSubscriptions = new Set(); + + private reconnectDelay = INITIAL_RECONNECT_DELAY; + private reconnectTimer: ReturnType | null = null; + private shouldReconnect = false; + + private pingTimer: ReturnType | null = null; + + constructor(config: PusherClientConfig) { + this.config = config; + } + + get connectionState(): ConnectionState { + return this.state; + } + + connect(): void { + if (this.ws) return; + + const token = this.config.getToken(); + if (!token) { + console.warn("[pusher] no token available, cannot connect"); + return; + } + + this.shouldReconnect = true; + this.setState( + this.state === "reconnecting" ? "reconnecting" : "connecting", + ); + + const url = `${this.config.url}?token=${encodeURIComponent(token)}`; + this.ws = new WebSocket(url); + + this.ws.onopen = () => { + this.setState("connected"); + this.reconnectDelay = INITIAL_RECONNECT_DELAY; + this.startPing(); + this.resubscribeAll(); + }; + + this.ws.onclose = () => { + this.cleanup(); + if (this.shouldReconnect) { + this.scheduleReconnect(); + } + }; + + this.ws.onerror = (event) => { + // onclose fires after onerror — reconnection is handled there. + logError(event, { scope: "pusher.ws" }); + }; + + this.ws.onmessage = (event) => { + this.handleMessage(event.data as string); + }; + } + + disconnect(): void { + this.shouldReconnect = false; + this.clearReconnectTimer(); + this.cleanup(); + this.activeSubscriptions.clear(); + this.setState("disconnected"); + } + + subscribe(channelId: string): void { + this.activeSubscriptions.add(channelId); + this.send({ type: "subscribe", channel: channelId }); + } + + unsubscribe(channelId: string): void { + this.activeSubscriptions.delete(channelId); + this.send({ type: "unsubscribe", channel: channelId }); + } + + sendMessage(channelId: string, payload: unknown): void { + this.send({ type: "message", channel: channelId, payload }); + } + + on( + channelId: string, + event: ChannelEventType, + callback: ChannelEventCallback, + ): void { + if (!this.listeners.has(channelId)) { + this.listeners.set(channelId, new Map()); + } + const channelListeners = this.listeners.get(channelId)!; + if (!channelListeners.has(event)) { + channelListeners.set(event, new Set()); + } + channelListeners.get(event)!.add(callback); + } + + off( + channelId: string, + event: ChannelEventType, + callback: ChannelEventCallback, + ): void { + const channelListeners = this.listeners.get(channelId); + if (!channelListeners) return; + const eventListeners = channelListeners.get(event); + if (!eventListeners) return; + eventListeners.delete(callback); + + if (eventListeners.size === 0) channelListeners.delete(event); + if (channelListeners.size === 0) this.listeners.delete(channelId); + } + + onStateChange(callback: (state: ConnectionState) => void): () => void { + this.stateListeners.add(callback); + return () => { + this.stateListeners.delete(callback); + }; + } + + // --- Private --- + + private send(msg: { + type: string; + channel?: string; + payload?: unknown; + }): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(msg)); + } + } + + private handleMessage(data: string): void { + if (data === "pong") return; + + let msg: ServerMessage; + try { + msg = JSON.parse(data); + } catch (err) { + logError(err, { scope: "pusher.parse", data }); + return; + } + + if (msg.type === "error") { + logError(new Error(msg.message ?? "pusher server error"), { + scope: "pusher.server", + }); + return; + } + + if (!msg.channel) return; + + const channelListeners = this.listeners.get(msg.channel); + if (!channelListeners) return; + + const eventListeners = channelListeners.get(msg.type as ChannelEventType); + if (!eventListeners) return; + + for (const cb of eventListeners) { + try { + cb(msg); + } catch (err) { + reportError(err, { scope: "pusher.listener", channel: msg.channel }); + } + } + } + + private resubscribeAll(): void { + for (const channelId of this.activeSubscriptions) { + this.send({ type: "subscribe", channel: channelId }); + } + } + + private scheduleReconnect(): void { + this.setState("reconnecting"); + + const jitter = Math.random() * 0.5 + 0.75; + const delay = Math.min(this.reconnectDelay * jitter, MAX_RECONNECT_DELAY); + + this.reconnectTimer = setTimeout(() => { + this.reconnectDelay = Math.min( + this.reconnectDelay * 2, + MAX_RECONNECT_DELAY, + ); + this.connect(); + }, delay); + } + + private cleanup(): void { + this.stopPing(); + if (this.ws) { + this.ws.onopen = null; + this.ws.onclose = null; + this.ws.onerror = null; + this.ws.onmessage = null; + if ( + this.ws.readyState === WebSocket.OPEN || + this.ws.readyState === WebSocket.CONNECTING + ) { + this.ws.close(); + } + this.ws = null; + } + } + + private clearReconnectTimer(): void { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } + + private startPing(): void { + this.stopPing(); + this.pingTimer = setInterval(() => { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send("ping"); + } + }, PING_INTERVAL); + } + + private stopPing(): void { + if (this.pingTimer) { + clearInterval(this.pingTimer); + this.pingTimer = null; + } + } + + private setState(state: ConnectionState): void { + if (this.state === state) return; + this.state = state; + for (const cb of this.stateListeners) { + cb(state); + } + } +} diff --git a/js/mobile/src/lib/pusher-provider.tsx b/js/mobile/src/lib/pusher-provider.tsx new file mode 100644 index 0000000..d458a2e --- /dev/null +++ b/js/mobile/src/lib/pusher-provider.tsx @@ -0,0 +1,67 @@ +import { + createContext, + useContext, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; +import { PusherClient, type ConnectionState } from "./pusher-client"; +import { useSessionStore } from "@/stores/session-store"; +import { appConfig } from "@/config/env"; + +const PusherContext = createContext(null); +const PusherStateContext = createContext("disconnected"); + +export function PusherProvider({ children }: { children: ReactNode }) { + const token = useSessionStore((s) => s.token); + const clientRef = useRef(null); + const [connectionState, setConnectionState] = + useState("disconnected"); + + useEffect(() => { + if (!token) { + if (clientRef.current) { + clientRef.current.disconnect(); + clientRef.current = null; + setConnectionState("disconnected"); + } + return; + } + + const client = new PusherClient({ + url: appConfig.pusherUrl, + getToken: () => useSessionStore.getState().token, + }); + + clientRef.current = client; + + const unsubscribeState = client.onStateChange((state) => { + setConnectionState(state); + }); + + client.connect(); + + return () => { + unsubscribeState(); + client.disconnect(); + clientRef.current = null; + }; + }, [token]); + + return ( + + + {children} + + + ); +} + +export function usePusherClient(): PusherClient | null { + return useContext(PusherContext); +} + +export function usePusherConnectionState(): ConnectionState { + return useContext(PusherStateContext); +} diff --git a/js/mobile/src/lib/query-client.ts b/js/mobile/src/lib/query-client.ts new file mode 100644 index 0000000..6776b63 --- /dev/null +++ b/js/mobile/src/lib/query-client.ts @@ -0,0 +1,56 @@ +import { + MutationCache, + QueryCache, + QueryClient, +} from "@tanstack/react-query"; +import { toast } from "sonner-native"; +import { ApiError, logError, reportError, toUserMessage } from "@/lib/errors"; + +declare module "@tanstack/react-query" { + interface Register { + queryMeta: { toastOnError?: boolean }; + mutationMeta: { suppressToast?: boolean }; + } +} + +function shouldRetryQuery(failureCount: number, err: unknown): boolean { + if (err instanceof ApiError) { + // Retry only on transient status codes; 4xx generally won't succeed on retry. + if (err.status === 408 || err.status === 429) return failureCount < 2; + if (err.status >= 400 && err.status < 500) return false; + } + return failureCount < 2; +} + +export function createQueryClient(): QueryClient { + return new QueryClient({ + defaultOptions: { + queries: { + retry: shouldRetryQuery, + refetchOnWindowFocus: false, + }, + mutations: { + // Mutations have side effects — never auto-retry. + retry: 0, + }, + }, + queryCache: new QueryCache({ + onError: (err, query) => { + logError(err, { scope: "query", queryKey: query.queryKey }); + if (query.meta?.toastOnError) { + toast.error(toUserMessage(err)); + } + }, + }), + mutationCache: new MutationCache({ + onError: (err, _variables, _context, mutation) => { + reportError(err, { + scope: "mutation", + mutationKey: mutation.options.mutationKey, + }); + if (mutation.meta?.suppressToast) return; + toast.error(toUserMessage(err)); + }, + }), + }); +} diff --git a/js/mobile/src/lib/random-name.ts b/js/mobile/src/lib/random-name.ts new file mode 100644 index 0000000..7053df5 --- /dev/null +++ b/js/mobile/src/lib/random-name.ts @@ -0,0 +1,19 @@ +const ADJECTIVES = [ + "amber", "bold", "calm", "crisp", "dark", "eager", "faint", "gentle", + "hasty", "icy", "jade", "keen", "lush", "misty", "nimble", "opal", + "pale", "quiet", "rapid", "sharp", "taut", "vivid", "warm", "zesty", + "bright", "clear", "deep", "fresh", "grand", "swift", +]; + +const NOUNS = [ + "arrow", "bloom", "cedar", "drift", "ember", "flint", "grove", "harbor", + "iris", "jewel", "knoll", "lake", "moss", "nova", "orbit", "petal", + "quartz", "ridge", "spark", "trail", "vale", "wave", "yarn", "zenith", + "brook", "cliff", "delta", "frost", "glow", "reef", +]; + +export function generateRandomName(): string { + const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]; + const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)]; + return `${adj}-${noun}`; +} diff --git a/js/mobile/src/lib/stream-visibility.ts b/js/mobile/src/lib/stream-visibility.ts new file mode 100644 index 0000000..c748769 --- /dev/null +++ b/js/mobile/src/lib/stream-visibility.ts @@ -0,0 +1,29 @@ +import { removeDuplicates } from "@/lib/utils"; + +const HUMAN_PREFIX = "human:"; +const NETWORK_PREFIX = "network:"; + +export type StreamVisibility = + | { mode: "network" } + | { mode: "custom"; humanIds: string[] }; + +export function parseVisibleTo( + visibleTo: string[], + networkId: string, +): StreamVisibility { + if (visibleTo.includes(`${NETWORK_PREFIX}${networkId}`)) { + return { mode: "network" }; + } + const humanIds = visibleTo + .filter((v) => v.startsWith(HUMAN_PREFIX)) + .map((v) => v.slice(HUMAN_PREFIX.length)); + return { mode: "custom", humanIds }; +} + +export function buildNetworkVisibility(networkId: string): string[] { + return [`${NETWORK_PREFIX}${networkId}`]; +} + +export function buildCustomVisibility(humanIds: string[]): string[] { + return removeDuplicates(humanIds).map((id) => `${HUMAN_PREFIX}${id}`); +} diff --git a/js/mobile/src/lib/time-utils.ts b/js/mobile/src/lib/time-utils.ts new file mode 100644 index 0000000..a4fd4a9 --- /dev/null +++ b/js/mobile/src/lib/time-utils.ts @@ -0,0 +1,20 @@ +const MINUTE = 60; +const HOUR = 3600; +const DAY = 86400; +const WEEK = 604800; +const MONTH = 2592000; +const YEAR = 31536000; + +export function formatDistanceToNow(date: Date | string): string { + const ms = typeof date === "string" ? new Date(date).getTime() : date.getTime(); + const seconds = Math.floor((Date.now() - ms) / 1000); + + if (seconds < 5) return "just now"; + if (seconds < MINUTE) return `${seconds}s ago`; + if (seconds < HOUR) return `${Math.floor(seconds / MINUTE)}m ago`; + if (seconds < DAY) return `${Math.floor(seconds / HOUR)}h ago`; + if (seconds < WEEK) return `${Math.floor(seconds / DAY)}d ago`; + if (seconds < MONTH) return `${Math.floor(seconds / WEEK)}w ago`; + if (seconds < YEAR) return `${Math.floor(seconds / MONTH)}mo ago`; + return `${Math.floor(seconds / YEAR)}y ago`; +} diff --git a/js/mobile/src/lib/upload.ts b/js/mobile/src/lib/upload.ts new file mode 100644 index 0000000..a6a571b --- /dev/null +++ b/js/mobile/src/lib/upload.ts @@ -0,0 +1,201 @@ +import { + FileSystemUploadType, + getInfoAsync, + uploadAsync, +} from "expo-file-system/legacy"; +import { apiClient } from "@/api/client"; +import { + createParticle, + createStreamParticle, +} from "@/lib/firestore-particles"; +import { + particlePath, + toFirestoreChildrenPath, + type ParticlePath, +} from "@/lib/particle-path"; + +interface UploadMediaParticleParams { + networkId: string; + /** Path of the destination container (stream — possibly with sub-segments). */ + targetPath: ParticlePath; + fileUri: string; + mimeType: string; + durationMs: number; + source: "camera" | "screen"; + createdByHumanId: string; +} + +/** + * Upload a recorded file and create the corresponding `media` particle in + * Firestore. Order matches desktop's `use-recorder` flow exactly: + * prepareUpload → PUT → confirmUpload → createParticle. + * + * Returns the new particle's id, or throws on any failure (no half-states — + * if any step fails the caller cancels and reports). + */ +export async function uploadMediaParticle({ + networkId, + targetPath, + fileUri, + mimeType, + durationMs, + source, + createdByHumanId, +}: UploadMediaParticleParams): Promise { + const info = await getInfoAsync(fileUri); + if (!info.exists || info.size === undefined) { + throw new Error("Recording file disappeared before upload."); + } + const sizeBytes = info.size; + + const namePrefix = mimeType.startsWith("audio/") ? "voice" : "video"; + const ext = extensionFromMime(mimeType); + const name = `${namePrefix}-${Date.now()}${ext}`; + + const { object_id, upload_url, upload_headers } = + await apiClient.prepareUpload({ + network_id: networkId, + name, + content_type: mimeType, + content_length: sizeBytes, + }); + + const uploadResult = await uploadAsync(upload_url, fileUri, { + httpMethod: "PUT", + uploadType: FileSystemUploadType.BINARY_CONTENT, + headers: upload_headers, + }); + + if (uploadResult.status < 200 || uploadResult.status >= 300) { + throw new Error( + `Upload to depot failed (HTTP ${uploadResult.status}).`, + ); + } + + await apiClient.confirmUpload(object_id); + + const collectionPath = toFirestoreChildrenPath(targetPath); + return createParticle( + collectionPath, + "media", + { + object_id, + mime_type: mimeType, + duration_ms: durationMs, + size_bytes: sizeBytes, + source, + }, + createdByHumanId, + ); +} + +interface CreateTextParticleParams { + networkId: string; + targetPath: ParticlePath; + content: string; + createdByHumanId: string; +} + +export async function createTextParticle({ + targetPath, + content, + createdByHumanId, +}: CreateTextParticleParams): Promise { + const collectionPath = toFirestoreChildrenPath(targetPath); + return createParticle( + collectionPath, + "text", + { content }, + createdByHumanId, + ); +} + +function extensionFromMime(mime: string): string { + if (mime === "video/mp4") return ".mp4"; + if (mime === "video/quicktime") return ".mov"; + if (mime === "audio/mp4") return ".m4a"; + if (mime === "audio/webm") return ".webm"; + return ""; +} + +// Helper kept here so callers can construct a fresh stream's child-path before +// the stream particle has been written. +export function streamChildrenPath( + networkId: string, + streamId: string, +): ParticlePath { + return particlePath(networkId, [streamId]); +} + +// --- New-stream flow --- + +interface CreateStreamWithFirstParticleParams { + networkId: string; + name: string; + /** ["network:{id}"] for everyone; ["human:{id}", ...] for specific people. */ + visibleTo: string[]; + createdByHumanId: string; + /** First particle to write into the new stream. Required — empty streams are not useful. */ + firstParticle: + | { type: "text"; content: string } + | { + type: "media"; + fileUri: string; + mimeType: string; + durationMs: number; + source: "camera" | "screen"; + }; +} + +interface CreateStreamWithFirstParticleResult { + streamId: string; +} + +/** + * Create a top-level stream particle plus its first child particle, in that + * order. Mirrors desktop's "create new stream" submit path (compose-overlay + * §handleStreamSubmit). On any failure the caller is responsible for retry — + * we don't roll back the stream particle on child failure because Firestore + * doesn't expose a multi-write transaction across these subcollections, and + * an empty stream is harmless (the user can retry composing into it). + */ +export async function createStreamWithFirstParticle({ + networkId, + name, + visibleTo, + createdByHumanId, + firstParticle, +}: CreateStreamWithFirstParticleParams): Promise { + // 1. The stream particle goes at the network root. + const rootChildrenPath = toFirestoreChildrenPath(particlePath(networkId, [])); + const streamId = await createStreamParticle( + rootChildrenPath, + { name }, + createdByHumanId, + visibleTo, + ); + + const streamPath = particlePath(networkId, [streamId]); + + // 2. The first child goes inside the new stream. + if (firstParticle.type === "text") { + await createTextParticle({ + networkId, + targetPath: streamPath, + content: firstParticle.content, + createdByHumanId, + }); + } else { + await uploadMediaParticle({ + networkId, + targetPath: streamPath, + fileUri: firstParticle.fileUri, + mimeType: firstParticle.mimeType, + durationMs: firstParticle.durationMs, + source: firstParticle.source, + createdByHumanId, + }); + } + + return { streamId }; +} diff --git a/js/mobile/src/lib/utils.ts b/js/mobile/src/lib/utils.ts new file mode 100644 index 0000000..b3229a2 --- /dev/null +++ b/js/mobile/src/lib/utils.ts @@ -0,0 +1,15 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function getInitials(email: string): string { + const prefix = email.split("@")[0] ?? ""; + return prefix.slice(0, 2).toUpperCase(); +} + +export function removeDuplicates(array: T[]): T[] { + return [...new Set(array)]; +} diff --git a/js/mobile/src/navigation/RootNavigator.tsx b/js/mobile/src/navigation/RootNavigator.tsx new file mode 100644 index 0000000..0bbf270 --- /dev/null +++ b/js/mobile/src/navigation/RootNavigator.tsx @@ -0,0 +1,55 @@ +import { createNativeStackNavigator } from "@react-navigation/native-stack"; +import { ActivityIndicator, View } from "react-native"; +import { useAuthStore } from "@/stores/auth-store"; +import { SignInScreen } from "@/features/auth/SignInScreen"; +import { NetworkListScreen } from "@/features/networks/NetworkListScreen"; +import { StreamListScreen } from "@/features/streams/StreamListScreen"; +import { NewStreamScreen } from "@/features/streams/NewStreamScreen"; +import { StreamViewScreen } from "@/features/stream-view/StreamViewScreen"; +import { SettingsScreen } from "@/features/settings/SettingsScreen"; +import { AccountScreen } from "@/features/settings/AccountScreen"; +import type { RootStackParamList } from "./types"; + +const Stack = createNativeStackNavigator(); + +export function RootNavigator() { + const status = useAuthStore((s) => s.status); + + if (status === "idle" || status === "restoring") { + return ( + + + + ); + } + + if (status === "unauthenticated") { + return ( + + + + ); + } + + return ( + + + + + + + + + ); +} diff --git a/js/mobile/src/navigation/types.ts b/js/mobile/src/navigation/types.ts new file mode 100644 index 0000000..dd11343 --- /dev/null +++ b/js/mobile/src/navigation/types.ts @@ -0,0 +1,22 @@ +import type { NativeStackScreenProps } from "@react-navigation/native-stack"; + +// Pure stack model from PRD §5. Drawer affordance lives inside the +// NetworkList screen itself, not the navigator — see Drawer.tsx. +export type RootStackParamList = { + SignIn: undefined; + NetworkList: undefined; + StreamList: { networkId: string }; + StreamView: { networkId: string; streamId: string }; + NewStream: { networkId: string }; + Settings: undefined; + Account: undefined; +}; + +export type RootStackScreenProps = + NativeStackScreenProps; + +declare global { + namespace ReactNavigation { + interface RootParamList extends RootStackParamList {} + } +} diff --git a/js/mobile/src/stores/auth-store.ts b/js/mobile/src/stores/auth-store.ts new file mode 100644 index 0000000..c291498 --- /dev/null +++ b/js/mobile/src/stores/auth-store.ts @@ -0,0 +1,137 @@ +import { create } from "zustand"; +import { + signInWithCustomToken, + signOut as firebaseSignOut, +} from "firebase/auth"; +import { apiClient } from "@/api/client"; +import type { Human } from "@/api/types"; +import { firebaseAuth } from "@/firebase"; +import { logError, ApiError } from "@/lib/errors"; +import { hydrateSession, useSessionStore } from "./session-store"; + +async function signInToFirebase(): Promise { + try { + const { token } = await apiClient.getFirebaseToken(); + await signInWithCustomToken(firebaseAuth, token); + } catch (err) { + // Firestore subscriptions will fail until the next successful sign-in; the + // rest of the app keeps working against Orion. Sentry catches the failure. + logError(err, { scope: "auth.firebase" }); + } +} + +type AuthStatus = "idle" | "restoring" | "unauthenticated" | "authenticated"; + +interface AuthState { + status: AuthStatus; + user: Human | null; + isRequestingCode: boolean; + isSigningIn: boolean; + isSigningOut: boolean; + error: string | null; + restoreSession: () => Promise; + requestCode: (email: string) => Promise; + signIn: (email: string, code: string) => Promise; + signOut: () => Promise; + clearError: () => void; +} + +export const useAuthStore = create((set) => ({ + status: "idle", + user: null, + isRequestingCode: false, + isSigningIn: false, + isSigningOut: false, + error: null, + + restoreSession: async () => { + set({ status: "restoring" }); + if (!useSessionStore.getState().hydrated) { + await hydrateSession(); + } + + const token = useSessionStore.getState().token; + if (!token) { + set({ status: "unauthenticated" }); + return; + } + + try { + const user = await apiClient.me(); + await signInToFirebase(); + set({ status: "authenticated", user }); + } catch (err) { + // Expected on expired/invalid tokens — fall back to the login screen. + logError(err, { scope: "auth.restore" }); + await useSessionStore.getState().clearToken(); + set({ status: "unauthenticated", user: null }); + } + }, + + requestCode: async (email: string) => { + set({ isRequestingCode: true, error: null }); + try { + await apiClient.requestCode({ email }); + } catch (e) { + const message = + e instanceof ApiError ? e.message : "Failed to send code"; + set({ error: message }); + throw e; + } finally { + set({ isRequestingCode: false }); + } + }, + + signIn: async (email: string, code: string) => { + set({ isSigningIn: true, error: null }); + try { + const { human, token } = await apiClient.signIn({ email, code }); + await useSessionStore.getState().setToken(token); + await signInToFirebase(); + set({ status: "authenticated", user: human }); + } catch (e) { + const message = e instanceof ApiError ? e.message : "Failed to sign in"; + set({ error: message }); + throw e; + } finally { + set({ isSigningIn: false }); + } + }, + + signOut: async () => { + set({ isSigningOut: true }); + try { + await apiClient.signOut(); + } catch (err) { + // Best-effort — sign out locally regardless. + logError(err, { scope: "auth.signOut" }); + } finally { + await firebaseSignOut(firebaseAuth).catch((err) => + logError(err, { scope: "auth.firebaseSignOut" }), + ); + await useSessionStore.getState().clearToken(); + set({ + status: "unauthenticated", + user: null, + isSigningOut: false, + error: null, + }); + } + }, + + clearError: () => set({ error: null }), +})); + +// React to token being cleared externally (e.g. 401 from API client). +useSessionStore.subscribe((state, prevState) => { + if (prevState.token && !state.token) { + const authState = useAuthStore.getState(); + if (authState.status === "authenticated") { + useAuthStore.setState({ + status: "unauthenticated", + user: null, + error: null, + }); + } + } +}); diff --git a/js/mobile/src/stores/playback-pause-store.ts b/js/mobile/src/stores/playback-pause-store.ts new file mode 100644 index 0000000..8885adf --- /dev/null +++ b/js/mobile/src/stores/playback-pause-store.ts @@ -0,0 +1,33 @@ +import { create } from "zustand"; + +/** + * Single source of truth for "is stream playback paused." Each component that + * wants to pause playback registers a unique id via `useSuspendPlayback`; the + * label is for devtools only. Playback is paused while any id is registered. + */ +interface PlaybackPauseState { + activeIds: Record; + composing: boolean; + add: (id: string, label: string) => void; + remove: (id: string) => void; + setComposing: (composing: boolean) => void; +} + +export const usePlaybackPauseStore = create((set) => ({ + activeIds: {}, + composing: false, + add: (id, label) => + set((s) => ({ activeIds: { ...s.activeIds, [id]: label } })), + remove: (id) => + set((s) => { + if (!(id in s.activeIds)) return s; + const { [id]: _, ...rest } = s.activeIds; + return { activeIds: rest }; + }), + setComposing: (composing) => set({ composing }), +})); + +export const selectIsPaused = (s: PlaybackPauseState) => + Object.keys(s.activeIds).length > 0 || s.composing; + +export const selectIsComposing = (s: PlaybackPauseState) => s.composing; diff --git a/js/mobile/src/stores/session-store.ts b/js/mobile/src/stores/session-store.ts new file mode 100644 index 0000000..2d24993 --- /dev/null +++ b/js/mobile/src/stores/session-store.ts @@ -0,0 +1,46 @@ +import * as SecureStore from "expo-secure-store"; +import { create } from "zustand"; + +const AUTH_TOKEN_KEY = "auth_token"; + +interface SessionState { + token: string | null; + /** + * False until SecureStore returns the persisted token (or confirms absence). + * The API client should treat requests as unauthenticated until this flips — + * see `useSessionStore.subscribe` in App.tsx for the bootstrap. + */ + hydrated: boolean; + setToken: (token: string) => Promise; + clearToken: () => Promise; +} + +export const useSessionStore = create((set) => ({ + token: null, + hydrated: false, + + setToken: async (token: string) => { + await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token); + set({ token }); + }, + + clearToken: async () => { + await SecureStore.deleteItemAsync(AUTH_TOKEN_KEY); + set({ token: null }); + }, +})); + +/** + * Bootstrap the session by reading SecureStore once. Call from App.tsx before + * mounting the navigator. Resolves after the store reflects whatever was in + * persistent storage. + */ +export async function hydrateSession(): Promise { + try { + const token = await SecureStore.getItemAsync(AUTH_TOKEN_KEY); + useSessionStore.setState({ token: token ?? null, hydrated: true }); + } catch { + // SecureStore failures are non-fatal — proceed unauthenticated. + useSessionStore.setState({ token: null, hydrated: true }); + } +} diff --git a/js/mobile/tailwind.config.js b/js/mobile/tailwind.config.js new file mode 100644 index 0000000..e39e396 --- /dev/null +++ b/js/mobile/tailwind.config.js @@ -0,0 +1,53 @@ +/** @type {import('tailwindcss').Config} */ +// Tokens mirror js/desktop/src/styles/globals.css. Desktop authors values in +// OKLCH; React Native's color parser is not guaranteed to handle oklch(), so +// we ship hex equivalents here and keep the OKLCH source in comments. +// +// Names match desktop 1:1 — components written against `bg-background`, +// `text-foreground`, etc. behave identically. +module.exports = { + content: ["./index.ts", "./src/**/*.{ts,tsx}"], + presets: [require("nativewind/preset")], + darkMode: "class", + theme: { + extend: { + colors: { + // ---- Light tokens (desktop :root) ---- + background: { DEFAULT: "#ffffff", dark: "#252525" }, // oklch(1 0 0) / oklch(0.145 0 0) + foreground: { DEFAULT: "#252525", dark: "#fafafa" }, // oklch(0.145 0 0) / oklch(0.985 0 0) + card: { DEFAULT: "#ffffff", dark: "#363636" }, // oklch(1 0 0) / oklch(0.205 0 0) + "card-foreground": { DEFAULT: "#252525", dark: "#fafafa" }, + popover: { DEFAULT: "#ffffff", dark: "#363636" }, + "popover-foreground": { DEFAULT: "#252525", dark: "#fafafa" }, + primary: { DEFAULT: "#363636", dark: "#ebebeb" }, // oklch(0.205 0 0) / oklch(0.922 0 0) + "primary-foreground": { DEFAULT: "#fafafa", dark: "#363636" }, + secondary: { DEFAULT: "#f4f4f4", dark: "#454545" }, // oklch(0.97 0 0) / oklch(0.269 0 0) + "secondary-foreground": { DEFAULT: "#363636", dark: "#fafafa" }, + muted: { DEFAULT: "#f4f4f4", dark: "#454545" }, + "muted-foreground": { DEFAULT: "#878787", dark: "#a6a6a6" }, // oklch(0.556 0 0) / oklch(0.708 0 0) + accent: { DEFAULT: "#f4f4f4", dark: "#454545" }, + "accent-foreground": { DEFAULT: "#363636", dark: "#fafafa" }, + destructive: { DEFAULT: "#dc2626", dark: "#ef4444" }, // oklch(0.577 0.245 27.325) / oklch(0.704 0.191 22.216) + border: { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.1)" }, + input: { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.15)" }, + ring: { DEFAULT: "#a6a6a6", dark: "#878787" }, // oklch(0.708 0 0) / oklch(0.556 0 0) + // Sidebar (drawer) tokens — desktop also defines these + sidebar: { DEFAULT: "#fafafa", dark: "#363636" }, + "sidebar-foreground": { DEFAULT: "#252525", dark: "#fafafa" }, + "sidebar-primary": { DEFAULT: "#363636", dark: "#6366f1" }, // chart-1 dark = oklch(0.488 0.243 264.376) + "sidebar-primary-foreground": { DEFAULT: "#fafafa", dark: "#fafafa" }, + "sidebar-accent": { DEFAULT: "#f4f4f4", dark: "#454545" }, + "sidebar-accent-foreground": { DEFAULT: "#363636", dark: "#fafafa" }, + "sidebar-border": { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.1)" }, + "sidebar-ring": { DEFAULT: "#a6a6a6", dark: "#878787" }, + }, + borderRadius: { + lg: "10px", // --radius: 0.625rem + md: "8px", + sm: "6px", + xl: "14px", + }, + }, + }, + plugins: [], +}; diff --git a/js/mobile/tsconfig.json b/js/mobile/tsconfig.json new file mode 100644 index 0000000..270bd74 --- /dev/null +++ b/js/mobile/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + }, + "types": [ + "nativewind/types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "nativewind-env.d.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/js/mobile/yarn.lock b/js/mobile/yarn.lock new file mode 100644 index 0000000..2945124 --- /dev/null +++ b/js/mobile/yarn.lock @@ -0,0 +1,5878 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@0no-co/graphql.web@^1.0.13", "@0no-co/graphql.web@^1.0.8": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz#296d00581bfaaabfda1e976849d927824aaea81b" + integrity sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw== + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.20.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/code-frame@~7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.0", "@babel/core@^7.25.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.20.5", "@babel/generator@^7.25.0", "@babel/generator@^7.29.0", "@babel/generator@^7.29.1": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz#611ff5482da9ef0db6291bcd24303400bca170fb" + integrity sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" + integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + regexpu-core "^6.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.5", "@babel/helper-define-polyfill-provider@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz#cf1e4462b613f2b54c41e6ff758d5dfcaa2c85d1" + integrity sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" + lodash.debounce "^4.0.8" + resolve "^1.22.11" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-member-expression-to-functions@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" + integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== + dependencies: + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.25.9", "@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.28.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz#4e349ff9222dab69a93a019cc296cdd8442e279a" + integrity sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ== + dependencies: + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + +"@babel/highlight@^7.10.4": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" + integrity sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.25.3", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.2.tgz#58bd50b9a7951d134988a1ae177a35ef9a703ba1" + integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/plugin-proposal-decorators@^7.12.9": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz#d159f26f78740e47bf3ef075882b155b2d54ca81" + integrity sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-decorators" "^7.28.6" + +"@babel/plugin-proposal-export-default-from@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz#59b050b0e5fdc366162ab01af4fcbac06ea40919" + integrity sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz#8c3293a0fef033e4c786b35ce1e159fc1d676153" + integrity sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz#8e19047560a8a48b11f1f5b46881f445f8692830" + integrity sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz#447559a225e66c4cd477a3ffb1a74d8c1fe25a62" + integrity sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-arrow-functions@^7.0.0-0", "@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-async-generator-functions@^7.25.4": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.29.0" + +"@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + +"@babel/plugin-transform-block-scoping@^7.25.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-properties@^7.0.0-0", "@babel/plugin-transform-class-properties@^7.25.4": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-static-block@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-classes@^7.0.0-0", "@babel/plugin-transform-classes@^7.25.4": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-transform-computed-properties@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" + +"@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" + integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-transform-export-namespace-from@^7.25.9": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23" + integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-flow-strip-types@^7.25.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz#5def3e1e7730f008d683144fb79b724f92c5cdf9" + integrity sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-flow" "^7.27.1" + +"@babel/plugin-transform-for-of@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-function-name@^7.25.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== + dependencies: + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-literals@^7.25.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-modules-commonjs@^7.24.8", "@babel/plugin-transform-modules-commonjs@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== + dependencies: + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-optional-chaining@^7.0.0-0", "@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-parameters@^7.24.7", "@babel/plugin-transform-parameters@^7.27.7": + version "7.27.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" + integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-methods@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-react-display-name@^7.24.7", "@babel/plugin-transform-react-display-name@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz#6f20a7295fea7df42eb42fed8f896813f5b934de" + integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-development@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98" + integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.27.1" + +"@babel/plugin-transform-react-jsx-self@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx@^7.25.2", "@babel/plugin-transform-react-jsx@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz#f51cb70a90b9529fbb71ee1f75ea27b7078eed62" + integrity sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-jsx" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/plugin-transform-react-pure-annotations@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz#339f1ce355eae242e0649f232b1c68907c02e879" + integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-regenerator@^7.24.7": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-runtime@^7.24.7": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz#a5fded13cc656700804bfd6e5ebd7fffd5266803" + integrity sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + babel-plugin-polyfill-corejs2 "^0.4.14" + babel-plugin-polyfill-corejs3 "^0.13.0" + babel-plugin-polyfill-regenerator "^0.6.5" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.0.0-0", "@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-spread@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-template-literals@^7.0.0-0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8" + integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typescript@^7.25.2", "@babel/plugin-transform-typescript@^7.28.5": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz#1e93d96da8adbefdfdade1d4956f73afa201a158" + integrity sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.28.6" + +"@babel/plugin-transform-unicode-regex@^7.0.0-0", "@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/preset-react@^7.22.15": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.28.5.tgz#6fcc0400fa79698433d653092c3919bb4b0878d9" + integrity sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-transform-react-display-name" "^7.28.0" + "@babel/plugin-transform-react-jsx" "^7.27.1" + "@babel/plugin-transform-react-jsx-development" "^7.27.1" + "@babel/plugin-transform-react-pure-annotations" "^7.27.1" + +"@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.23.0": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz#540359efa3028236958466342967522fd8f2a60c" + integrity sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-typescript" "^7.28.5" + +"@babel/runtime@^7.20.0", "@babel/runtime@^7.25.0": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.2.tgz#9a6e2d05f4b6692e1801cd4fb176ad823930ed5e" + integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== + +"@babel/template@^7.25.0", "@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/traverse@^7.23.0", "@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.25.2", "@babel/types@^7.26.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@egjs/hammerjs@^2.0.17": + version "2.0.17" + resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" + integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A== + dependencies: + "@types/hammerjs" "^2.0.36" + +"@expo/cli@54.0.24": + version "54.0.24" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.24.tgz#7225d99e019f6eb85fd5ef018d8d82391be5dc82" + integrity sha512-5xse1bEgnVUBhOrtttc6xTNJVvjyTRavpzuF0/0nuj+312vfSbk7EiRbG+xJ2pW/iZxnhLPJkFCrPYG0nmheAQ== + dependencies: + "@0no-co/graphql.web" "^1.0.8" + "@expo/code-signing-certificates" "^0.0.6" + "@expo/config" "~12.0.13" + "@expo/config-plugins" "~54.0.4" + "@expo/devcert" "^1.2.1" + "@expo/env" "~2.0.8" + "@expo/image-utils" "^0.8.8" + "@expo/json-file" "^10.0.8" + "@expo/metro" "~54.2.0" + "@expo/metro-config" "~54.0.15" + "@expo/osascript" "^2.3.8" + "@expo/package-manager" "^1.9.10" + "@expo/plist" "^0.4.8" + "@expo/prebuild-config" "^54.0.8" + "@expo/schema-utils" "^0.1.8" + "@expo/spawn-async" "^1.7.2" + "@expo/ws-tunnel" "^1.0.1" + "@expo/xcpretty" "^4.3.0" + "@react-native/dev-middleware" "0.81.5" + "@urql/core" "^5.0.6" + "@urql/exchange-retry" "^1.3.0" + accepts "^1.3.8" + arg "^5.0.2" + better-opn "~3.0.2" + bplist-creator "0.1.0" + bplist-parser "^0.3.1" + chalk "^4.0.0" + ci-info "^3.3.0" + compression "^1.7.4" + connect "^3.7.0" + debug "^4.3.4" + env-editor "^0.4.1" + expo-server "^1.0.6" + freeport-async "^2.0.0" + getenv "^2.0.0" + glob "^13.0.0" + lan-network "^0.2.1" + minimatch "^9.0.0" + node-forge "^1.3.3" + npm-package-arg "^11.0.0" + ora "^3.4.0" + picomatch "^4.0.3" + pretty-bytes "^5.6.0" + pretty-format "^29.7.0" + progress "^2.0.3" + prompts "^2.3.2" + qrcode-terminal "0.11.0" + require-from-string "^2.0.2" + requireg "^0.2.2" + resolve "^1.22.2" + resolve-from "^5.0.0" + resolve.exports "^2.0.3" + semver "^7.6.0" + send "^0.19.0" + slugify "^1.3.4" + source-map-support "~0.5.21" + stacktrace-parser "^0.1.10" + structured-headers "^0.4.1" + tar "^7.5.2" + terminal-link "^2.1.1" + undici "^6.18.2" + wrap-ansi "^7.0.0" + ws "^8.12.1" + +"@expo/code-signing-certificates@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz#6b7b22830cb69c77a45e357c2f3aa7ab436ac772" + integrity sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w== + dependencies: + node-forge "^1.3.3" + +"@expo/config-plugins@~54.0.4": + version "54.0.4" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-54.0.4.tgz#b31cb16f6651342abcdafba600118245ecd9fb00" + integrity sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q== + dependencies: + "@expo/config-types" "^54.0.10" + "@expo/json-file" "~10.0.8" + "@expo/plist" "^0.4.8" + "@expo/sdk-runtime-versions" "^1.0.0" + chalk "^4.1.2" + debug "^4.3.5" + getenv "^2.0.0" + glob "^13.0.0" + resolve-from "^5.0.0" + semver "^7.5.4" + slash "^3.0.0" + slugify "^1.6.6" + xcode "^3.0.1" + xml2js "0.6.0" + +"@expo/config-types@^54.0.10": + version "54.0.10" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-54.0.10.tgz#688f4338255d2fdea970f44e2dfd8e8d37dec292" + integrity sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA== + +"@expo/config@~12.0.13": + version "12.0.13" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-12.0.13.tgz#8e696e6121c3c364e1dd527f595cf0a1d9386828" + integrity sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ== + dependencies: + "@babel/code-frame" "~7.10.4" + "@expo/config-plugins" "~54.0.4" + "@expo/config-types" "^54.0.10" + "@expo/json-file" "^10.0.8" + deepmerge "^4.3.1" + getenv "^2.0.0" + glob "^13.0.0" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + resolve-workspace-root "^2.0.0" + semver "^7.6.0" + slugify "^1.3.4" + sucrase "~3.35.1" + +"@expo/devcert@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.2.1.tgz#1a687985bea1670866e54d5ba7c0ced963c354f4" + integrity sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA== + dependencies: + "@expo/sudo-prompt" "^9.3.1" + debug "^3.1.0" + +"@expo/devtools@0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@expo/devtools/-/devtools-0.1.8.tgz#bc5b297698f78b3b67037f04593a31e688330a7a" + integrity sha512-SVLxbuanDjJPgc0sy3EfXUMLb/tXzp6XIHkhtPVmTWJAp+FOr6+5SeiCfJrCzZFet0Ifyke2vX3sFcKwEvCXwQ== + dependencies: + chalk "^4.1.2" + +"@expo/env@~2.0.8": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@expo/env/-/env-2.0.11.tgz#3a10d9142b1833566bdfb39de1c062f7a8b8ac38" + integrity sha512-xV+ps6YCW7XIPVUwFVCRN2nox09dnRwy8uIjwHWTODu0zFw4kp4omnVkl0OOjuu2XOe7tdgAHxikrkJt9xB/7Q== + dependencies: + chalk "^4.0.0" + debug "^4.3.4" + dotenv "~16.4.5" + dotenv-expand "~11.0.6" + getenv "^2.0.0" + +"@expo/fingerprint@0.15.5": + version "0.15.5" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.15.5.tgz#76968191026346657d22cea10d957aa8be97777e" + integrity sha512-mdVoAMcux1WlM6kd1RoWiHRNqKqS+J6mKmWQ/BKgeh937S/fcW58EE68O6nc4KDXtWi3PBeNHskOFcgyIuD4hw== + dependencies: + "@expo/spawn-async" "^1.7.2" + arg "^5.0.2" + chalk "^4.1.2" + debug "^4.3.4" + getenv "^2.0.0" + glob "^13.0.0" + ignore "^5.3.1" + minimatch "^10.2.2" + p-limit "^3.1.0" + resolve-from "^5.0.0" + semver "^7.6.0" + +"@expo/image-utils@^0.8.8": + version "0.8.13" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.8.13.tgz#c7476352af9f576440e5ec8201c2f75f090a4804" + integrity sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA== + dependencies: + "@expo/require-utils" "^55.0.4" + "@expo/spawn-async" "^1.7.2" + chalk "^4.0.0" + getenv "^2.0.0" + jimp-compact "0.16.1" + parse-png "^2.1.0" + semver "^7.6.0" + +"@expo/json-file@^10.0.13", "@expo/json-file@^10.0.8", "@expo/json-file@~10.0.8": + version "10.0.13" + resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-10.0.13.tgz#1a9ac56333786e8672181b0b95aab08f8255a548" + integrity sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA== + dependencies: + "@babel/code-frame" "^7.20.0" + json5 "^2.2.3" + +"@expo/metro-config@54.0.15", "@expo/metro-config@~54.0.15": + version "54.0.15" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.15.tgz#aafdd2c2627fa60927e2d307f4d8cd303b6c5169" + integrity sha512-SqIya4VZ9KHM1S9g+xR0A+QKw1Tfs7Gacx6bQNJ98vs4+O7I5+QP5mHZIB0QSZLUV8opiXebHYTiTu+0OAsIUw== + dependencies: + "@babel/code-frame" "^7.20.0" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.5" + "@expo/config" "~12.0.13" + "@expo/env" "~2.0.8" + "@expo/json-file" "~10.0.8" + "@expo/metro" "~54.2.0" + "@expo/spawn-async" "^1.7.2" + browserslist "^4.25.0" + chalk "^4.1.0" + debug "^4.3.2" + dotenv "~16.4.5" + dotenv-expand "~11.0.6" + getenv "^2.0.0" + glob "^13.0.0" + hermes-parser "^0.29.1" + jsc-safe-url "^0.2.4" + lightningcss "^1.30.1" + picomatch "^4.0.3" + postcss "~8.4.32" + resolve-from "^5.0.0" + +"@expo/metro@~54.2.0": + version "54.2.0" + resolved "https://registry.yarnpkg.com/@expo/metro/-/metro-54.2.0.tgz#6ecf4a77ae7553b73daca4206854728de76c854d" + integrity sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w== + dependencies: + metro "0.83.3" + metro-babel-transformer "0.83.3" + metro-cache "0.83.3" + metro-cache-key "0.83.3" + metro-config "0.83.3" + metro-core "0.83.3" + metro-file-map "0.83.3" + metro-minify-terser "0.83.3" + metro-resolver "0.83.3" + metro-runtime "0.83.3" + metro-source-map "0.83.3" + metro-symbolicate "0.83.3" + metro-transform-plugins "0.83.3" + metro-transform-worker "0.83.3" + +"@expo/osascript@^2.3.8": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@expo/osascript/-/osascript-2.4.2.tgz#fe341cff1eb2c939da43cf58ade5504c8a5d77ca" + integrity sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw== + dependencies: + "@expo/spawn-async" "^1.7.2" + +"@expo/package-manager@^1.9.10": + version "1.10.4" + resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.10.4.tgz#1a16bd2ccf85a23865dd98392c11b9f75f9bbf7a" + integrity sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ== + dependencies: + "@expo/json-file" "^10.0.13" + "@expo/spawn-async" "^1.7.2" + chalk "^4.0.0" + npm-package-arg "^11.0.0" + ora "^3.4.0" + resolve-workspace-root "^2.0.0" + +"@expo/plist@^0.4.8": + version "0.4.8" + resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.4.8.tgz#e014511a4a5008cf2b832b91caa8e9f2704127cc" + integrity sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ== + dependencies: + "@xmldom/xmldom" "^0.8.8" + base64-js "^1.2.3" + xmlbuilder "^15.1.1" + +"@expo/prebuild-config@^54.0.8": + version "54.0.8" + resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-54.0.8.tgz#509410345489cc52d1e6ece52742384efe7ad7c6" + integrity sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg== + dependencies: + "@expo/config" "~12.0.13" + "@expo/config-plugins" "~54.0.4" + "@expo/config-types" "^54.0.10" + "@expo/image-utils" "^0.8.8" + "@expo/json-file" "^10.0.8" + "@react-native/normalize-colors" "0.81.5" + debug "^4.3.1" + resolve-from "^5.0.0" + semver "^7.6.0" + xml2js "0.6.0" + +"@expo/require-utils@^55.0.4": + version "55.0.4" + resolved "https://registry.yarnpkg.com/@expo/require-utils/-/require-utils-55.0.4.tgz#cd474a8997ba6ecfa43d084a7f17bde0cb854179" + integrity sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA== + dependencies: + "@babel/code-frame" "^7.20.0" + "@babel/core" "^7.25.2" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + +"@expo/schema-utils@^0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@expo/schema-utils/-/schema-utils-0.1.8.tgz#8b9543d77fc4ac4954196e3fa00f8fcedd71426a" + integrity sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A== + +"@expo/sdk-runtime-versions@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c" + integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ== + +"@expo/spawn-async@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.7.2.tgz#fcfe66c3e387245e72154b1a7eae8cada6a47f58" + integrity sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew== + dependencies: + cross-spawn "^7.0.3" + +"@expo/sudo-prompt@^9.3.1": + version "9.3.2" + resolved "https://registry.yarnpkg.com/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz#0fd2813402a42988e49145cab220e25bea74b308" + integrity sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw== + +"@expo/vector-icons@^15.0.3": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-15.1.1.tgz#4b1d2c60493c0b0536972f0a5babd5f5c85b48f4" + integrity sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw== + +"@expo/ws-tunnel@^1.0.1": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz#92b70e7264ad42ea07f28a20f2f540b91d07bdd9" + integrity sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q== + +"@expo/xcpretty@^4.3.0": + version "4.4.3" + resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.4.3.tgz#c49a052591cc672a9a032760c0262c76d46e08b6" + integrity sha512-wC562eD3gS6vO2tWHToFhlFnmHKfKHgF1oyvojeSkLK/ZYop1bMU+7cOMiF9Sq70CzcsLy/EMRy/uRc76QmNRw== + dependencies: + "@babel/code-frame" "^7.20.0" + chalk "^4.1.0" + js-yaml "^4.1.0" + +"@firebase/ai@2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.11.1.tgz#a78c8d8a8acc5261fb2e0fa0216209b43a57c6dc" + integrity sha512-WGTF81W3WBKJY+c7xqTzO15OGAkCAs8cpADqflAI0skhTZjIkhF0qyf55rq4Ctt6jKygkv99rPfMrjAHTgXaVQ== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/analytics-compat@0.2.27": + version "0.2.27" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.27.tgz#58ef74a91930267923577f07ca371182d9f7ce2e" + integrity sha512-ZObpYpAxL6JfgH7GnvlDD0sbzGZ0o4nijV8skatV9ZX49hJtCYbFqaEcPYptT94rgX1KUoKEderC7/fa7hybtw== + dependencies: + "@firebase/analytics" "0.10.21" + "@firebase/analytics-types" "0.8.3" + "@firebase/component" "0.7.2" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f" + integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== + +"@firebase/analytics@0.10.21": + version "0.10.21" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.21.tgz#109d95d287acefe3d8276835291dbbcf4688c18c" + integrity sha512-j2y2q65BlgLGB5Pwjhv/Jopw2X/TBTzvAtI5z/DSp56U4wBj7LfhBfzbdCtFPges+Wz0g55GdoawXibOH5jGng== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/installations" "0.6.21" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.4.2.tgz#c0b3808ebe9a366d3b2cba295eb7a1587de66314" + integrity sha512-M91NhxqbSkI0ChkJWy69blC+rPr6HEgaeRllddSaU1pQ/7IiegeCQM9pPDIgvWnwnBSzKhUHpe6ro/jhJ+cvzw== + dependencies: + "@firebase/app-check" "0.11.2" + "@firebase/app-check-types" "0.5.3" + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a" + integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== + +"@firebase/app-check-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5" + integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== + +"@firebase/app-check@0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.11.2.tgz#c7f771c5222d77a810978081e7b493d3f5e8968f" + integrity sha512-jcXQVMHAQ5AEKzVD5C7s5fmAYeFOuN6lAJeNTgZK2B9aLnofWaJt8u1A8Idm8gpsBBYSaY3cVyeH5SWMOVPBLQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/app-compat@0.5.11": + version "0.5.11" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.11.tgz#4cb54f447cb03446465a05d00a71509b5f2ec620" + integrity sha512-KaACDjXkK5VLpI01vEs592R7/8s5DjFdIXfKoR385ly1SmK3Tu+jMHCIB4MsiY5jsez6v7VlEX/3rJ90dVkHyA== + dependencies: + "@firebase/app" "0.14.11" + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/app-types@0.9.4": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.4.tgz#e85864332b591db95a10620668405bef6906947b" + integrity sha512-crX9TA5SVYZwLPG7/R16IsH8FLlgkPXjJUVhsVpHVDSqJiq3D/NuFTM5ctxGTExXAOeIn//69tQw47CPerM8MQ== + dependencies: + "@firebase/logger" "0.5.0" + +"@firebase/app@0.14.11": + version "0.14.11" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.11.tgz#150f5f98299c24569fab8fbbffb7295075f526d7" + integrity sha512-yxADFW35LYkP8oSGobGsYIrI42I+GPCvKTNHx4meT9Yq3C950IVz1eANoBk822I9tbKv1wyv9P4Bv1G5TpucFw== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.6.5.tgz#d12d27a4c2230d3ee32ef5c200ebd3b9108528ca" + integrity sha512-IfVsafZ3QiXbsydXTP/XMI0wVYbJLI1rkb8Qqf03/h5FnL+upbbPOb+6Yj3RpcX+Y1iP5Uh18lxTHlXfbiyAow== + dependencies: + "@firebase/auth" "1.13.0" + "@firebase/auth-types" "0.13.0" + "@firebase/component" "0.7.2" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/auth-interop-types@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0" + integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== + +"@firebase/auth-types@0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9" + integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg== + +"@firebase/auth@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.13.0.tgz#da83853b465c1ab4b638e542d78df6b3c4855a15" + integrity sha512-mKkSLNym3UbnnZ06dAmtqzp5EpPGCANGCZDJbkoR135aoUdKG6Aizwcnp29RzsQpwH0nmy5nay17Sfbsh9oY8A== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/component@0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.7.2.tgz#82a87848ad389d6037019745c973e4f2779a6983" + integrity sha512-iyVDGc6Vjx7Rm0cAdccLH/NG6fADsgJak/XW9IA2lPf8AjIlsemOpFGKczYyPHxm4rnKdR8z6sK4+KEC7NwmEg== + dependencies: + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/data-connect@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.6.0.tgz#c4581d13685eccac724895b8c7292df4a24cd334" + integrity sha512-OiugPRcdlhqXF97oR9CjVObILmsWU0dFUS0gXNYEe4bDfpW8pZmQ5GqhIPPtLWbT/0W2lMJJD7VILFMk+xuHPg== + dependencies: + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/database-compat@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.1.3.tgz#19a512209afbba9710d7febc52cd875e1b239e3c" + integrity sha512-GMyfWjD8mehjg/QpNkY/tl9G/MoeugPeg91n9D0atggxbWuKF/2KhVPHZDH+XmoP0EKYqMWYTtKxBsaBaNKLYQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/database" "1.1.2" + "@firebase/database-types" "1.0.19" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/database-types@1.0.19": + version "1.0.19" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.19.tgz#0e59454ea764aa2617fb2a8ea94104c4cb1605ac" + integrity sha512-FqewjUZmV9LqFfuEnmgdcUpiOUz7qwLXxnm/H8BcMFEzQXtd1yyUDm8ex5VRad2nuTE+ahOuCjUAM/cyDncO+g== + dependencies: + "@firebase/app-types" "0.9.4" + "@firebase/util" "1.15.0" + +"@firebase/database@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.1.2.tgz#cd99d7f205d7eee121e2c327bf89b42c3afa3776" + integrity sha512-lP96CMjMPy/+d1d9qaaHjHHdzdwvEOuyyLq9ehX89e2XMKwS1jHNzYBO+42bdSumuj5ukPbmnFtViZu8YOMT+w== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.4.8": + version "0.4.8" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.8.tgz#00651c9d01f940d9906b34ae2cc4229527f4b7f8" + integrity sha512-WK9NJRpnosGD2nuyjdr7K+Ht7AxRYJlTF62myI4rRA7ibJOosbecvjacR5oirJ7s1BgNS6qzcBw7n4fD3a5w1w== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/firestore" "4.14.0" + "@firebase/firestore-types" "3.0.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1" + integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== + +"@firebase/firestore@4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.14.0.tgz#a17801fe2e8a0095fc655d38cd791c4a48058cb4" + integrity sha512-bZc6YOjRkMBVA16527tgzi6iN9n//xRB3Mmx/R+Gr6UAP/+xrIKOejQIcn1hh+tCzNT8jO0jI+kWox5J4tB/qQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + "@firebase/webchannel-wrapper" "1.0.5" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + +"@firebase/functions-compat@0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.3.tgz#bb7e5b8330143db594466c1140267799cf41680d" + integrity sha512-BxkEwWgx1of0tKaao/r2VR6WBLk/RAiyztatiONPrPE8gkitFkOnOCxf8i9cUyA5hX5RGt5H30uNn25Q6QNEmQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/functions" "0.13.3" + "@firebase/functions-types" "0.6.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654" + integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== + +"@firebase/functions@0.13.3": + version "0.13.3" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.3.tgz#e923cb6f6763531cbe909253155920abb4105f04" + integrity sha512-csO7ckK3SSs+NUZW1nms9EK7ckHe/1QOjiP8uAkCYa7ND18s44vjE9g3KxEeIUpyEPqZaX1EhJuFyZjHigAcYw== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.2" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/installations-compat@0.2.21": + version "0.2.21" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.21.tgz#c00e1e1b3957aff0957cff80a776109895328c54" + integrity sha512-zahIUkaVKbR8zmTeBHkdfaVl6JGWlhVoSjF7CVH33nFqD3SlPEpEEegn2GNT5iAfsVdtlCyJJ9GW4YKjq+RJKQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/installations" "0.6.21" + "@firebase/installations-types" "0.5.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f" + integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== + +"@firebase/installations@0.6.21": + version "0.6.21" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.21.tgz#38c9a5487c7ccc7dd4328736556afad8eebf453e" + integrity sha512-xGFGTeICJZ5vhrmmDukeczIcFULFXybojML2+QSDFoKj5A7zbGN7KzFGSKNhDkIxpjzsYG9IleJyUebuAcmqWA== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/util" "1.15.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/logger@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.5.0.tgz#a9e55b1c669a0983dc67127fa4a5964ce8ed5e1b" + integrity sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.25": + version "0.2.25" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.25.tgz#1fd6f317d303dfab57ec51409eb41c89080f2dd6" + integrity sha512-eoOQqGLtRlseTdiemTN44LlHZpltK5gnhq8XVUuLgtIOG+odtDzrz2UoTpcJWSzaJQVxNLb/x9f39tHdDM4N4w== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/messaging" "0.12.25" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e" + integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== + +"@firebase/messaging@0.12.25": + version "0.12.25" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.25.tgz#465135006a5d728efaeea1d35790f0e6e20a3e54" + integrity sha512-7RhDwoDHlOK1/ou0/LeubxmjcngsTjDdrY/ssg2vwAVpUuVAhQzQvuCAOYxcX5wNC1zCgQ54AP1vdngBwbCmOQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/installations" "0.6.21" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.15.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.24": + version "0.2.24" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.24.tgz#1c970640119a8839f7447f624de365f150f21399" + integrity sha512-YRlejH8wLt7ThWao+HXoKUHUrZKGYq+otxkPS+8nuE5PeN1cBXX7NAJl9ueuUkBwMIrnKdnDqL/voHXxDAAt3g== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/performance" "0.7.11" + "@firebase/performance-types" "0.2.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb" + integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== + +"@firebase/performance@0.7.11": + version "0.7.11" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.11.tgz#238a805f78c5411f1d41c4cdba7854ca4115a1b7" + integrity sha512-V3uAhrz7IYJuji+OgT3qYTGKxpek/TViXti9OSsUJ4AexZ3jQjYH5Yrn7JvBxk8MGiSLsC872hh+BxQiPZsm7g== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/installations" "0.6.21" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + web-vitals "^4.2.4" + +"@firebase/remote-config-compat@0.2.23": + version "0.2.23" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.23.tgz#3614d83c1f22c68152793bbbf6965715e1716d07" + integrity sha512-4+KqRRHEUUmKT6tFmnpWATOsaFfmSuBs1jXH8JzVtMLEYqq/WS9IDM92OdefFDSrAA2xGd0WN004z8mKeIIscw== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/logger" "0.5.0" + "@firebase/remote-config" "0.8.2" + "@firebase/remote-config-types" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz#f0f503b32edda3384f5252f9900cd9613adbb99c" + integrity sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg== + +"@firebase/remote-config@0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.8.2.tgz#389d2b01d4d877c6cb13bf85692dfda45d61fe6d" + integrity sha512-5EXqOThV4upjK9D38d/qOSVwOqRhemlaOFk9vCkMNNALeIlwr+4pLjtLNo4qoY8etQmU/1q4aIATE9N8PFqg0g== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/installations" "0.6.21" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/storage-compat@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.4.2.tgz#866e3bfc4533510bc1d6207d10e9ef5748359cf4" + integrity sha512-R+aB38wxCH5zjIO/xu9KznI7fgiPuZAG98uVm1NcidHyyupGgIDLKigGmRGBZMnxibe/m2oxNKoZpfEbUX2aQQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/storage" "0.14.2" + "@firebase/storage-types" "0.8.3" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d" + integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== + +"@firebase/storage@0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.14.2.tgz#476bad2594ad26487b232620f0a23991d65ab69a" + integrity sha512-o/culaTeJ8GRpKXRJov21rux/n9dRaSOWLebyatFP2sqEdCxQPjVA1H9Z2fzYwQxMIU0JVmC7SPPmU11v7L6vQ== + dependencies: + "@firebase/component" "0.7.2" + "@firebase/util" "1.15.0" + tslib "^2.1.0" + +"@firebase/util@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.15.0.tgz#783c1a67dc0690dbe3afca668174c11368843008" + integrity sha512-AmWf3cHAOMbrCPG4xdPKQaj5iHnyYfyLKZxwz+Xf55bqKbpAmcYifB4jQinT2W9XhDRHISOoPyBOariJpCG6FA== + dependencies: + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz#39cf5a600450cb42f1f0b507cc385459bf103b27" + integrity sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw== + +"@grpc/grpc-js@~1.9.0": + version "1.9.15" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.15.tgz#433d7ac19b1754af690ea650ab72190bd700739b" + integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.8": + version "0.7.15" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" + integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.5" + yargs "^17.7.2" + +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + +"@isaacs/ttlcache@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" + integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.6.tgz#8dc9afa2ac1506cb1a58f89940f1c124446c8df3" + integrity sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw== + +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.5.tgz#d9315ad7cf3f30aac70bda3c068443dc6f143659" + integrity sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0", "@protobufjs/inquire@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.1.tgz#6cb936f4ac50965230af1e9d0bbfd57ea3675aa4" + integrity sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.1.tgz#eaee5900122c110a3dbcb728c0597014a2621774" + integrity sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg== + +"@react-native-async-storage/async-storage@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz#a3aa565253e46286655560172f4e366e8969f5ad" + integrity sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw== + dependencies: + merge-options "^3.0.4" + +"@react-native/assets-registry@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.81.5.tgz#d22c924fa6f6d4a463c5af34ce91f38756c0fa7d" + integrity sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w== + +"@react-native/babel-plugin-codegen@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.81.5.tgz#328d03f42c32b5a8cc2dee1aa84a7c48dddc5f18" + integrity sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ== + dependencies: + "@babel/traverse" "^7.25.3" + "@react-native/codegen" "0.81.5" + +"@react-native/babel-preset@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.81.5.tgz#e8b7969d21f87ef4e41e603248e8a70c44b4a5bb" + integrity sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA== + dependencies: + "@babel/core" "^7.25.2" + "@babel/plugin-proposal-export-default-from" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-default-from" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.4" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.25.0" + "@babel/plugin-transform-class-properties" "^7.25.4" + "@babel/plugin-transform-classes" "^7.25.4" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" + "@babel/plugin-transform-flow-strip-types" "^7.25.2" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.25.1" + "@babel/plugin-transform-literals" "^7.25.2" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-react-display-name" "^7.24.7" + "@babel/plugin-transform-react-jsx" "^7.25.2" + "@babel/plugin-transform-react-jsx-self" "^7.24.7" + "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-runtime" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-typescript" "^7.25.2" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/template" "^7.25.0" + "@react-native/babel-plugin-codegen" "0.81.5" + babel-plugin-syntax-hermes-parser "0.29.1" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.14.0" + +"@react-native/codegen@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.81.5.tgz#d4dec668c94b9d58a5c2dbdbf026db331e1b6b27" + integrity sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g== + dependencies: + "@babel/core" "^7.25.2" + "@babel/parser" "^7.25.3" + glob "^7.1.1" + hermes-parser "0.29.1" + invariant "^2.2.4" + nullthrows "^1.1.1" + yargs "^17.6.2" + +"@react-native/community-cli-plugin@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.81.5.tgz#617789cda4da419d03dda00e2a78c36188b4391e" + integrity sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw== + dependencies: + "@react-native/dev-middleware" "0.81.5" + debug "^4.4.0" + invariant "^2.2.4" + metro "^0.83.1" + metro-config "^0.83.1" + metro-core "^0.83.1" + semver "^7.1.3" + +"@react-native/debugger-frontend@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.81.5.tgz#82ece0181e9a7a3dcbdfa86cf9decd654e13f81f" + integrity sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w== + +"@react-native/dev-middleware@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.81.5.tgz#81e8ac545d7736ef6ebb2e59fdbaebc5cf9aedec" + integrity sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA== + dependencies: + "@isaacs/ttlcache" "^1.4.1" + "@react-native/debugger-frontend" "0.81.5" + chrome-launcher "^0.15.2" + chromium-edge-launcher "^0.2.0" + connect "^3.6.5" + debug "^4.4.0" + invariant "^2.2.4" + nullthrows "^1.1.1" + open "^7.0.3" + serve-static "^1.16.2" + ws "^6.2.3" + +"@react-native/gradle-plugin@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.81.5.tgz#a58830f38789f6254b64449a17fe57455b589d00" + integrity sha512-hORRlNBj+ReNMLo9jme3yQ6JQf4GZpVEBLxmTXGGlIL78MAezDZr5/uq9dwElSbcGmLEgeiax6e174Fie6qPLg== + +"@react-native/js-polyfills@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.81.5.tgz#2ca68188c8fff9b951f507b1dec7efe928848274" + integrity sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w== + +"@react-native/normalize-colors@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.81.5.tgz#1ca6cb6772bb7324df2b11aab35227eacd6bdfe7" + integrity sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g== + +"@react-native/virtualized-lists@0.81.5": + version "0.81.5" + resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz#24123fded16992d7e46ecc4ccd473be939ea8c1b" + integrity sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw== + dependencies: + invariant "^2.2.4" + nullthrows "^1.1.1" + +"@react-navigation/core@^7.17.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.17.2.tgz#8a17b73faf7c0688a4749dcac8c7350d8f93e943" + integrity sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA== + dependencies: + "@react-navigation/routers" "^7.5.3" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + query-string "^7.1.3" + react-is "^19.1.0" + use-latest-callback "^0.2.4" + use-sync-external-store "^1.5.0" + +"@react-navigation/elements@^2.9.15": + version "2.9.15" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.15.tgz#384380f8d7f0c5b35e960aa5a6bdf4cc2bcb78b4" + integrity sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA== + dependencies: + color "^4.2.3" + use-latest-callback "^0.2.4" + use-sync-external-store "^1.5.0" + +"@react-navigation/native-stack@^7.2.0": + version "7.14.12" + resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.14.12.tgz#b22faf70271056a4648c4601707d4b33f0e2e88d" + integrity sha512-dUfpkrVeVKKV8iqXsmoUp3Rv0iH3YaB3eZwScru/FlcqAp/r3/qA6zEXkGX9hZK+/ziWAPFrf1frBSNbgOYSFQ== + dependencies: + "@react-navigation/elements" "^2.9.15" + color "^4.2.3" + sf-symbols-typescript "^2.1.0" + warn-once "^0.1.1" + +"@react-navigation/native@^7.0.14": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.2.2.tgz#c9438fe8393454d74fdb7f959ac9abede52b1f8e" + integrity sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w== + dependencies: + "@react-navigation/core" "^7.17.2" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + use-latest-callback "^0.2.4" + +"@react-navigation/routers@^7.5.3": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.5.3.tgz#8002930ef5f62351be2475d0dffde3ffaee174d7" + integrity sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg== + dependencies: + nanoid "^3.3.11" + +"@sinclair/typebox@^0.27.8": + version "0.27.10" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6" + integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tanstack/query-core@5.100.6": + version "5.100.6" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.100.6.tgz#2f6dc032aaae1f327d85d3b977e6d530c948a91f" + integrity sha512-Os2CPUr98to98RYm+D4qGqGkiffn7MGSyl2547a4MljVkHE30AMJRqTiyCqBfMwzAx/I91vCkAxp5tHSla6Twg== + +"@tanstack/react-query@^5.90.21": + version "5.100.6" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.100.6.tgz#f69aefc2136b1e7126e630e0d7881cf7b7e25c77" + integrity sha512-uVSrps0PV16Cxmcn2rvL+dUhwTpTUtiRW347AEeYxMZXO2pZe9ja7E24PAMGoQ5u2g89DD8u4QhOviBk+RN8RA== + dependencies: + "@tanstack/query-core" "5.100.6" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/hammerjs@^2.0.36": + version "2.0.46" + resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.46.tgz#381daaca1360ff8a7c8dff63f32e69745b9fb1e1" + integrity sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "25.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.6.0.tgz#4e09bad9b469871f2d0f68140198cbd714f4edca" + integrity sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ== + dependencies: + undici-types "~7.19.0" + +"@types/react@~19.1.0": + version "19.1.17" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.17.tgz#8be0b9c546cede389b930a98eb3fad1897f209c3" + integrity sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA== + dependencies: + csstype "^3.0.2" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" + +"@ungap/structured-clone@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@urql/core@^5.0.6", "@urql/core@^5.1.2": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@urql/core/-/core-5.2.0.tgz#77ee41e192e261fea30c2ca6c2f340410b45d214" + integrity sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A== + dependencies: + "@0no-co/graphql.web" "^1.0.13" + wonka "^6.3.2" + +"@urql/exchange-retry@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz#042ff5f3512a062651ec7257f1b07f9db2f6fefd" + integrity sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg== + dependencies: + "@urql/core" "^5.1.2" + wonka "^6.3.2" + +"@xmldom/xmldom@^0.8.8": + version "0.8.13" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.13.tgz#00d1dd940b218dff2e49309d410d8bb212159225" + integrity sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw== + +"@xmldom/xmldom@^0.9.10": + version "0.9.10" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.9.10.tgz#a0ad5a26fe8aa996310870726e1704977f769dee" + integrity sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@^1.3.7, accepts@^1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn@^8.15.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + +ajv@^8.11.0: + version "8.20.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" + integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +anser@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" + integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-polyfill-corejs2@^0.4.14: + version "0.4.17" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz#198f970f1c99a856b466d1187e88ce30bd199d91" + integrity sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.8" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" + integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.5" + core-js-compat "^3.43.0" + +babel-plugin-polyfill-regenerator@^0.6.5: + version "0.6.8" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz#8a6bfd5dd54239362b3d06ce47ac52b2d95d7721" + integrity sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.8" + +babel-plugin-react-compiler@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz#bdf7360a23a4d5ebfca090255da3893efd07425f" + integrity sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw== + dependencies: + "@babel/types" "^7.26.0" + +babel-plugin-react-native-web@~0.21.0: + version "0.21.2" + resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz#d2f7fd673278da82577aa583457edb55d9cccbe0" + integrity sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA== + +babel-plugin-syntax-hermes-parser@0.29.1, babel-plugin-syntax-hermes-parser@^0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz#09ca9ecb0330eba1ef939b6d3f1f55bb06a9dc33" + integrity sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA== + dependencies: + hermes-parser "0.29.1" + +babel-plugin-transform-flow-enums@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz#d1d0cc9bdc799c850ca110d0ddc9f21b9ec3ef25" + integrity sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ== + dependencies: + "@babel/plugin-syntax-flow" "^7.12.1" + +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-expo@~54.0.10: + version "54.0.10" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz#3b70f4af3a5f65f945d7957ef511ee016e8f2fd6" + integrity sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/plugin-proposal-decorators" "^7.12.9" + "@babel/plugin-proposal-export-default-from" "^7.24.7" + "@babel/plugin-syntax-export-default-from" "^7.24.7" + "@babel/plugin-transform-class-static-block" "^7.27.1" + "@babel/plugin-transform-export-namespace-from" "^7.25.9" + "@babel/plugin-transform-flow-strip-types" "^7.25.2" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-runtime" "^7.24.7" + "@babel/preset-react" "^7.22.15" + "@babel/preset-typescript" "^7.23.0" + "@react-native/babel-preset" "0.81.5" + babel-plugin-react-compiler "^1.0.0" + babel-plugin-react-native-web "~0.21.0" + babel-plugin-syntax-hermes-parser "^0.29.1" + babel-plugin-transform-flow-enums "^0.0.2" + debug "^4.3.4" + resolve-from "^5.0.0" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +baseline-browser-mapping@^2.10.12: + version "2.10.24" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz#6dc320c7bf53859ec2bf55d54db6d2e5c078df16" + integrity sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA== + +better-opn@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817" + integrity sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ== + dependencies: + open "^8.0.4" + +big-integer@1.6.x: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +bplist-creator@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" + integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg== + dependencies: + stream-buffers "2.2.x" + +bplist-parser@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" + integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== + dependencies: + big-integer "1.6.x" + +bplist-parser@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.2.tgz#3ac79d67ec52c4c107893e0237eb787cbacbced7" + integrity sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ== + dependencies: + big-integer "1.6.x" + +brace-expansion@^1.1.7: + version "1.1.14" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b" + integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.0.tgz#4f41a41190216ee36067ec381526fe9539c4f0ae" + integrity sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== + dependencies: + balanced-match "^1.0.0" + +brace-expansion@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" + integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== + dependencies: + balanced-match "^4.0.2" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0, browserslist@^4.25.0, browserslist@^4.28.1: + version "4.28.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + dependencies: + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.4.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001782: + version "1.0.30001791" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz#dfb93d85c40ad380c57123e72e10f3c575786b51" + integrity sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ== + +chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + +chrome-launcher@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" + integrity sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + +chromium-edge-launcher@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz#0c378f28c99aefc360705fa155de0113997f62fc" + integrity sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0, ci-info@^3.3.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^2.0.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +commander@^12.0.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +comment-json@^4.2.5: + version "4.6.2" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.6.2.tgz#235d8a908e211855b0068248a794afddb87670af" + integrity sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w== + dependencies: + array-timsort "^1.0.3" + esprima "^4.0.1" + +compressible@~2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.8.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== + dependencies: + bytes "3.1.2" + compressible "~2.0.18" + debug "2.6.9" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect@^3.6.5, connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.43.0: + version "3.49.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.49.0.tgz#06145447d92f4aaf258a0c44f24b47afaeaffef6" + integrity sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA== + dependencies: + browserslist "^4.28.1" + +cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^5.1.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.2.2.tgz#01b6e8d163637bb2dd6c982ca4ed65863682786e" + integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.1.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" + integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +debug@2.6.9, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decode-uri-component@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +depd@2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dotenv-expand@~11.0.6: + version "11.0.7" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz#af695aea007d6fdc84c86cd8d0ad7beb40a0bd08" + integrity sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA== + dependencies: + dotenv "^16.4.5" + +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dotenv@~16.4.5: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.328: + version "1.5.344" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz#6437cc08a7d9b914a98120e182f37793c9eaffd4" + integrity sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +env-editor@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861" + integrity sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA== + +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +expo-asset@~12.0.13: + version "12.0.13" + resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-12.0.13.tgz#1974ed7abee2ad987a519dbdcbf7f0c647dddf5b" + integrity sha512-x/p7WvQUnkn6K43b9eL6SPeq5Vnf1E8BDe9bDrWrvMqzyUvJnUFvl+ctg3034s/+UHe7Ne2pAmc0+yzbl8CrDQ== + dependencies: + "@expo/image-utils" "^0.8.8" + expo-constants "~18.0.13" + +expo-audio@~1.0.13: + version "1.0.16" + resolved "https://registry.yarnpkg.com/expo-audio/-/expo-audio-1.0.16.tgz#6cee98909c5c8b641832e73d944cf0be1c16d4de" + integrity sha512-j7otyjtO+8PVbemoCoRBr2Em0Kv9to3bfz5UpI5tDVVb5gD1dkn7sjv6/W6tWNqx14WLR1Wijh/ACecVv1Py+g== + +expo-build-properties@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/expo-build-properties/-/expo-build-properties-1.0.10.tgz#2c3fb4248f78828e952defa636635a653e3ad546" + integrity sha512-mFCZbrbrv0AP5RB151tAoRzwRJelqM7bCJzCkxpu+owOyH+p/rFC/q7H5q8B9EpVWj8etaIuszR+gKwohpmu1Q== + dependencies: + ajv "^8.11.0" + semver "^7.6.0" + +expo-camera@~17.0.10: + version "17.0.10" + resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-17.0.10.tgz#b3a217f0eb811a6e3522c2aff9f42be578aa6456" + integrity sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg== + dependencies: + invariant "^2.2.4" + +expo-constants@~18.0.13: + version "18.0.13" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-18.0.13.tgz#0117f1f3d43be7b645192c0f4f431fb4efc4803d" + integrity sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ== + dependencies: + "@expo/config" "~12.0.13" + "@expo/env" "~2.0.8" + +expo-file-system@~19.0.16, expo-file-system@~19.0.22: + version "19.0.22" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-19.0.22.tgz#8e8f892b2e89a78102b2b90fc1af5bb6bad4f21b" + integrity sha512-l9pgahSc7sJD0bP9vBNeXvZjy8QKDpVHVxWmei/ESQOrzmoj5BidziqLVsyZdxsi+PfdbTtttLTAmddH/JafYA== + +expo-font@~14.0.11: + version "14.0.11" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-14.0.11.tgz#198743d17332520545107df026d8a261e6b2732f" + integrity sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg== + dependencies: + fontfaceobserver "^2.1.0" + +expo-haptics@~15.0.7: + version "15.0.8" + resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-15.0.8.tgz#f93f895ac5d76fe0c5ac26b3644e1dbb097833f3" + integrity sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g== + +expo-keep-awake@~15.0.8: + version "15.0.8" + resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz#911c5effeba9baff2ccde79ef0ff5bf856215f8d" + integrity sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ== + +expo-modules-autolinking@3.0.25: + version "3.0.25" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-3.0.25.tgz#3cb6e88fb491f15e3152c03eb64964e0b22fb707" + integrity sha512-YmHWctJlwvOuLZccg3cOXvSiXVJrPMKl7g2YR0YHWoGL9v2RvcmgaPJWPSLVW+voNEgEPsbo5UmUrAqbnYcBeg== + dependencies: + "@expo/spawn-async" "^1.7.2" + chalk "^4.1.0" + commander "^7.2.0" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + +expo-modules-core@3.0.30: + version "3.0.30" + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-3.0.30.tgz#ec4e216a33083afb0ddf314615d6d31a741984d6" + integrity sha512-a6IrpAn/Jbmwxi9L+hMmXKpNqnkUpoF7WHOpn02rVLyax2J0gB1vvCVE5rNydplEnt41Q6WxQwvcOjZaIkcSUg== + dependencies: + invariant "^2.2.4" + +expo-secure-store@~15.0.8: + version "15.0.8" + resolved "https://registry.yarnpkg.com/expo-secure-store/-/expo-secure-store-15.0.8.tgz#678065599bb76061b5a85b15b9426bf7a11089ae" + integrity sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw== + +expo-server@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/expo-server/-/expo-server-1.0.6.tgz#a821c55dd3f9f3f9bce0ba70d81bbc9fcc360ee8" + integrity sha512-vb5TBtskvEdzYuW79lATXutOEBfW5m6U4EFpNjCVZTnI7S//SAsLQkYEpn+EDfn84m6VQfzSGkIVR6YPaScKFA== + +expo-status-bar@~3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-3.0.9.tgz#87cfc803fa614f09a985b8e75e3dd7abd51ce2cb" + integrity sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw== + dependencies: + react-native-is-edge-to-edge "^1.2.1" + +expo-video@~3.0.10: + version "3.0.16" + resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-3.0.16.tgz#8160bd33fe2e898519d3c18a404567a30d81d4f2" + integrity sha512-H1HlxcHGomZItqisGfW3YL/G9BHtNBfVSimDJcLuyxyU87wFnV8loO9tCjuhufkfh/aTa2sW5BYAjLjg9DvnBQ== + +expo@~54.0.0: + version "54.0.34" + resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.34.tgz#fb1c90ff9d65d58978198622808c66a2d3b66fcc" + integrity sha512-XkVHguZZDC8BcTQxHAd14/TQFbDp1Wt0Z/KApO9t68Ll5A127hLCPzU+a9gytfCIiyL/V1IpF1vIcOLKEVAoNQ== + dependencies: + "@babel/runtime" "^7.20.0" + "@expo/cli" "54.0.24" + "@expo/config" "~12.0.13" + "@expo/config-plugins" "~54.0.4" + "@expo/devtools" "0.1.8" + "@expo/fingerprint" "0.15.5" + "@expo/metro" "~54.2.0" + "@expo/metro-config" "54.0.15" + "@expo/vector-icons" "^15.0.3" + "@ungap/structured-clone" "^1.3.0" + babel-preset-expo "~54.0.10" + expo-asset "~12.0.13" + expo-constants "~18.0.13" + expo-file-system "~19.0.22" + expo-font "~14.0.11" + expo-keep-awake "~15.0.8" + expo-modules-autolinking "3.0.25" + expo-modules-core "3.0.30" + pretty-format "^29.7.0" + react-refresh "^0.14.2" + whatwg-url-without-unicode "8.0.0-3" + +exponential-backoff@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6" + integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + dependencies: + reusify "^1.0.4" + +faye-websocket@0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +firebase@^12.10.0: + version "12.12.1" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.12.1.tgz#4c5145ce819509b1e547d27aef584ab719809d29" + integrity sha512-ee7xA+bTJLfjB9BP/8FQr3EkxmpAAGc1lNc5QkWgTDpUw24HYXFPm7FEWRdLtGnygxIdYpFmepSc5VjkI6NHhw== + dependencies: + "@firebase/ai" "2.11.1" + "@firebase/analytics" "0.10.21" + "@firebase/analytics-compat" "0.2.27" + "@firebase/app" "0.14.11" + "@firebase/app-check" "0.11.2" + "@firebase/app-check-compat" "0.4.2" + "@firebase/app-compat" "0.5.11" + "@firebase/app-types" "0.9.4" + "@firebase/auth" "1.13.0" + "@firebase/auth-compat" "0.6.5" + "@firebase/data-connect" "0.6.0" + "@firebase/database" "1.1.2" + "@firebase/database-compat" "2.1.3" + "@firebase/firestore" "4.14.0" + "@firebase/firestore-compat" "0.4.8" + "@firebase/functions" "0.13.3" + "@firebase/functions-compat" "0.4.3" + "@firebase/installations" "0.6.21" + "@firebase/installations-compat" "0.2.21" + "@firebase/messaging" "0.12.25" + "@firebase/messaging-compat" "0.2.25" + "@firebase/performance" "0.7.11" + "@firebase/performance-compat" "0.2.24" + "@firebase/remote-config" "0.8.2" + "@firebase/remote-config-compat" "0.2.23" + "@firebase/storage" "0.14.2" + "@firebase/storage-compat" "0.4.2" + "@firebase/util" "1.15.0" + +flow-enums-runtime@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" + integrity sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw== + +fontfaceobserver@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8" + integrity sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg== + +freeport-async@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4" + integrity sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ== + +fresh@~0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +getenv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/getenv/-/getenv-2.0.0.tgz#b1698c7b0f29588f4577d06c42c73a5b475c69e0" + integrity sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^13.0.0: + version "13.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== + dependencies: + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" + +glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.3.tgz#5e5c2b15b60370a4c7930c383dfb76bf17bc403c" + integrity sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg== + dependencies: + function-bind "^1.1.2" + +hermes-estree@0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5" + integrity sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ== + +hermes-estree@0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.0.tgz#bb7da6613ab8e67e334a1854ea1e209f487d307b" + integrity sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ== + +hermes-estree@0.35.0: + version "0.35.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.35.0.tgz#767cce0b14a68b4bc06cd5db7efe889f6188c565" + integrity sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg== + +hermes-parser@0.29.1, hermes-parser@^0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.29.1.tgz#436b24bcd7bb1e71f92a04c396ccc0716c288d56" + integrity sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA== + dependencies: + hermes-estree "0.29.1" + +hermes-parser@0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.32.0.tgz#7916984ef6fdce62e7415d354cf35392061cd303" + integrity sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw== + dependencies: + hermes-estree "0.32.0" + +hermes-parser@0.35.0: + version "0.35.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.35.0.tgz#7625ec2f34ab897c2a17a7bea9788d136d5fd8c9" + integrity sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA== + dependencies: + hermes-estree "0.35.0" + +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hosted-git-info@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" + integrity sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w== + dependencies: + lru-cache "^10.0.1" + +http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +http-parser-js@>=0.5.1: + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + +https-proxy-agent@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +idb@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" + integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +image-size@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.2.1.tgz#ee118aedfe666db1a6ee12bed5821cde3740276d" + integrity sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw== + dependencies: + queue "6.0.2" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3, inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-arrayish@^0.3.1: + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jimp-compact@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.1.tgz#9582aea06548a2c1e04dd148d7c3ab92075aefa3" + integrity sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww== + +jiti@^1.21.7: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +jsc-safe-url@^0.2.2, jsc-safe-url@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" + integrity sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q== + +jsesc@^3.0.2, jsesc@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lan-network@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/lan-network/-/lan-network-0.2.1.tgz#e4764a0d17f6bd1f2794c838fa219526a1b756f8" + integrity sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lighthouse-logger@^1.0.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa" + integrity sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g== + dependencies: + debug "^2.6.9" + marky "^1.2.2" + +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + +lightningcss-darwin-arm64@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz#565bd610533941cba648a70e105987578d82f996" + integrity sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ== + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss-darwin-x64@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz#c906a267237b1c7fe08bff6c5ac032c099bc9482" + integrity sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg== + +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz#a7c3c4d6ee18dffeb8fa69f14f8f9267f7dc0c34" + integrity sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz#c7c16432a571ec877bf734fe500e4a43d48c2814" + integrity sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz#cfd9e18df1cd65131da286ddacfa3aee6862a752" + integrity sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz#6682ff6b9165acef9a6796bd9127a8e1247bb0ed" + integrity sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz#714221212ad184ddfe974bbb7dbe9300dfde4bc0" + integrity sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz#247958daf622a030a6dc2285afa16b7184bdf21e" + integrity sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz#64cfe473c264ef5dc275a4d57a516d77fcac6bc9" + integrity sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz#237d0dc87d9cdc9cf82536bcbc07426fa9f3f422" + integrity sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@^1.30.1: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.32.0" + lightningcss-darwin-arm64 "1.32.0" + lightningcss-darwin-x64 "1.32.0" + lightningcss-freebsd-x64 "1.32.0" + lightningcss-linux-arm-gnueabihf "1.32.0" + lightningcss-linux-arm64-gnu "1.32.0" + lightningcss-linux-arm64-musl "1.32.0" + lightningcss-linux-x64-gnu "1.32.0" + lightningcss-linux-x64-musl "1.32.0" + lightningcss-win32-arm64-msvc "1.32.0" + lightningcss-win32-x64-msvc "1.32.0" + +lightningcss@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.27.0.tgz#d4608e63044343836dd9769f6c8b5d607867649a" + integrity sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ== + dependencies: + detect-libc "^1.0.3" + optionalDependencies: + lightningcss-darwin-arm64 "1.27.0" + lightningcss-darwin-x64 "1.27.0" + lightningcss-freebsd-x64 "1.27.0" + lightningcss-linux-arm-gnueabihf "1.27.0" + lightningcss-linux-arm64-gnu "1.27.0" + lightningcss-linux-arm64-musl "1.27.0" + lightningcss-linux-x64-gnu "1.27.0" + lightningcss-linux-x64-musl "1.27.0" + lightningcss-win32-arm64-msvc "1.27.0" + lightningcss-win32-x64-msvc "1.27.0" + +lilconfig@^3.1.1, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^10.0.1: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^11.0.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.5.tgz#29047d348c0b2793e3112a01c739bb7c6d855637" + integrity sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lucide-react-native@^0.575.0: + version "0.575.0" + resolved "https://registry.yarnpkg.com/lucide-react-native/-/lucide-react-native-0.575.0.tgz#8ce8e555d7c0ebb88cd529966256f803125b74ce" + integrity sha512-kdGcjF4Rm1YKuNs3IaW5lDAqVKn9RBj1Fmjt3JBr08PMIXpVV7iL0ICNF/awiPZQicHlx/v9xgyZZS4TAFxDNg== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +marky@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/marky/-/marky-1.3.0.tgz#422b63b0baf65022f02eda61a238eccdbbc14997" + integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ== + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +memoize-one@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + +merge-options@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +metro-babel-transformer@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz#d8c134615530c9ee61364526d44ca4bb0c5343ea" + integrity sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g== + dependencies: + "@babel/core" "^7.25.2" + flow-enums-runtime "^0.0.6" + hermes-parser "0.32.0" + nullthrows "^1.1.1" + +metro-babel-transformer@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.83.7.tgz#8448c7a550571de87d00e97c1a7139c6b6900e4a" + integrity sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA== + dependencies: + "@babel/core" "^7.25.2" + flow-enums-runtime "^0.0.6" + hermes-parser "0.35.0" + metro-cache-key "0.83.7" + nullthrows "^1.1.1" + +metro-cache-key@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.83.3.tgz#ae6c5d4eb1ad8d06a92bf7294ca730a8d880b573" + integrity sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-cache-key@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.83.7.tgz#59e647cf6dd6297e43d0bd7ab927db48c6ba80b5" + integrity sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-cache@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.83.3.tgz#f1245cc48570c47d8944495e61d67f0228f10172" + integrity sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q== + dependencies: + exponential-backoff "^3.1.1" + flow-enums-runtime "^0.0.6" + https-proxy-agent "^7.0.5" + metro-core "0.83.3" + +metro-cache@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.83.7.tgz#73ab7857ba6267b78f6374396829d660a67deccf" + integrity sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg== + dependencies: + exponential-backoff "^3.1.1" + flow-enums-runtime "^0.0.6" + https-proxy-agent "^7.0.5" + metro-core "0.83.7" + +metro-config@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.83.3.tgz#a30e7a69b5cf8c4ac4c4b68b1b4c33649ae129a2" + integrity sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA== + dependencies: + connect "^3.6.5" + flow-enums-runtime "^0.0.6" + jest-validate "^29.7.0" + metro "0.83.3" + metro-cache "0.83.3" + metro-core "0.83.3" + metro-runtime "0.83.3" + yaml "^2.6.1" + +metro-config@0.83.7, metro-config@^0.83.1: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.83.7.tgz#fc88f4f75992744d6d64bf27c6a2f11b10e9fcfb" + integrity sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q== + dependencies: + connect "^3.6.5" + flow-enums-runtime "^0.0.6" + jest-validate "^29.7.0" + metro "0.83.7" + metro-cache "0.83.7" + metro-core "0.83.7" + metro-runtime "0.83.7" + yaml "^2.6.1" + +metro-core@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.83.3.tgz#007e93f7d1983777da8988dfb103ad897c9835b8" + integrity sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw== + dependencies: + flow-enums-runtime "^0.0.6" + lodash.throttle "^4.1.1" + metro-resolver "0.83.3" + +metro-core@0.83.7, metro-core@^0.83.1: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.83.7.tgz#45cc9eebd979c75021015f190dbd023e833bdd16" + integrity sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg== + dependencies: + flow-enums-runtime "^0.0.6" + lodash.throttle "^4.1.1" + metro-resolver "0.83.7" + +metro-file-map@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.83.3.tgz#3d79fbb1d379ab178dd895ce54cb5ecb183d74a2" + integrity sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA== + dependencies: + debug "^4.4.0" + fb-watchman "^2.0.0" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-worker "^29.7.0" + micromatch "^4.0.4" + nullthrows "^1.1.1" + walker "^1.0.7" + +metro-file-map@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.83.7.tgz#1d0d47db8a76631f0fed2112edad5fec462aec50" + integrity sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw== + dependencies: + debug "^4.4.0" + fb-watchman "^2.0.0" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-worker "^29.7.0" + micromatch "^4.0.4" + nullthrows "^1.1.1" + walker "^1.0.7" + +metro-minify-terser@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz#c1c70929c86b14c8bf03e6321b4f9310bc8dbe87" + integrity sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ== + dependencies: + flow-enums-runtime "^0.0.6" + terser "^5.15.0" + +metro-minify-terser@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.83.7.tgz#2de0b70f8cd58e9383b014313a1d9e7babe8d878" + integrity sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ== + dependencies: + flow-enums-runtime "^0.0.6" + terser "^5.15.0" + +metro-resolver@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.83.3.tgz#06207bdddc280b9335722a8c992aeec017413942" + integrity sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-resolver@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.83.7.tgz#84b2e2749f0dba0e1c204764d01c21892157cf1c" + integrity sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-runtime@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.83.3.tgz#ff504df5d93f38b1af396715b327e589ba8d184d" + integrity sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw== + dependencies: + "@babel/runtime" "^7.25.0" + flow-enums-runtime "^0.0.6" + +metro-runtime@0.83.7, metro-runtime@^0.83.1: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.83.7.tgz#3606a71c94a4ef862b7a0e43156b35f7a4e4cf17" + integrity sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ== + dependencies: + "@babel/runtime" "^7.25.0" + flow-enums-runtime "^0.0.6" + +metro-source-map@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.83.3.tgz#04bb464f7928ea48bcdfd18912c8607cf317c898" + integrity sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg== + dependencies: + "@babel/traverse" "^7.25.3" + "@babel/traverse--for-generate-function-map" "npm:@babel/traverse@^7.25.3" + "@babel/types" "^7.25.2" + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-symbolicate "0.83.3" + nullthrows "^1.1.1" + ob1 "0.83.3" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-source-map@0.83.7, metro-source-map@^0.83.1: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.83.7.tgz#6208e72427c987e66a9ec23ebaee0b3cc76dd16c" + integrity sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw== + dependencies: + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-symbolicate "0.83.7" + nullthrows "^1.1.1" + ob1 "0.83.7" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-symbolicate@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz#67af03950f0dfe19a7c059e3983e39a31e95d03a" + integrity sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw== + dependencies: + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-source-map "0.83.3" + nullthrows "^1.1.1" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-symbolicate@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.83.7.tgz#ee10cfbafb5ed5ae5f04a565c496fd772afb3f5c" + integrity sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw== + dependencies: + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-source-map "0.83.7" + nullthrows "^1.1.1" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-transform-plugins@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz#2c59ba841e269363cf3acb13138cb992f0c75013" + integrity sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.3" + flow-enums-runtime "^0.0.6" + nullthrows "^1.1.1" + +metro-transform-plugins@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.83.7.tgz#9fc9b4c209ca2fdc1e720a9fefde7c20f32ab5ad" + integrity sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + flow-enums-runtime "^0.0.6" + nullthrows "^1.1.1" + +metro-transform-worker@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz#ca6ae4a02b0f61b33299e6e56bacaba32dcd607f" + integrity sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/types" "^7.25.2" + flow-enums-runtime "^0.0.6" + metro "0.83.3" + metro-babel-transformer "0.83.3" + metro-cache "0.83.3" + metro-cache-key "0.83.3" + metro-minify-terser "0.83.3" + metro-source-map "0.83.3" + metro-transform-plugins "0.83.3" + nullthrows "^1.1.1" + +metro-transform-worker@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.83.7.tgz#1ba8980f660630f7dfbf52fcf2b7bd6e4ae4528b" + integrity sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + flow-enums-runtime "^0.0.6" + metro "0.83.7" + metro-babel-transformer "0.83.7" + metro-cache "0.83.7" + metro-cache-key "0.83.7" + metro-minify-terser "0.83.7" + metro-source-map "0.83.7" + metro-transform-plugins "0.83.7" + nullthrows "^1.1.1" + +metro@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.83.3.tgz#1e7e04c15519af746f8932c7f9c553d92c39e922" + integrity sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/core" "^7.25.2" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.3" + "@babel/types" "^7.25.2" + accepts "^1.3.7" + chalk "^4.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^4.4.0" + error-stack-parser "^2.0.6" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + hermes-parser "0.32.0" + image-size "^1.0.2" + invariant "^2.2.4" + jest-worker "^29.7.0" + jsc-safe-url "^0.2.2" + lodash.throttle "^4.1.1" + metro-babel-transformer "0.83.3" + metro-cache "0.83.3" + metro-cache-key "0.83.3" + metro-config "0.83.3" + metro-core "0.83.3" + metro-file-map "0.83.3" + metro-resolver "0.83.3" + metro-runtime "0.83.3" + metro-source-map "0.83.3" + metro-symbolicate "0.83.3" + metro-transform-plugins "0.83.3" + metro-transform-worker "0.83.3" + mime-types "^2.1.27" + nullthrows "^1.1.1" + serialize-error "^2.1.0" + source-map "^0.5.6" + throat "^5.0.0" + ws "^7.5.10" + yargs "^17.6.2" + +metro@0.83.7, metro@^0.83.1: + version "0.83.7" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.83.7.tgz#8357495dfea9e11d34ea15c0a1e2acd508ee5559" + integrity sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + accepts "^2.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^4.4.0" + error-stack-parser "^2.0.6" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + hermes-parser "0.35.0" + image-size "^1.0.2" + invariant "^2.2.4" + jest-worker "^29.7.0" + jsc-safe-url "^0.2.2" + lodash.throttle "^4.1.1" + metro-babel-transformer "0.83.7" + metro-cache "0.83.7" + metro-cache-key "0.83.7" + metro-config "0.83.7" + metro-core "0.83.7" + metro-file-map "0.83.7" + metro-resolver "0.83.7" + metro-runtime "0.83.7" + metro-source-map "0.83.7" + metro-symbolicate "0.83.7" + metro-transform-plugins "0.83.7" + metro-transform-worker "0.83.7" + mime-types "^3.0.1" + nullthrows "^1.1.1" + serialize-error "^2.1.0" + source-map "^0.5.6" + throat "^5.0.0" + ws "^7.5.10" + yargs "^17.6.2" + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^2.1.27, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^10.2.2: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.0: + version "9.0.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== + dependencies: + brace-expansion "^2.0.2" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + dependencies: + minipass "^7.1.2" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.11, nanoid@^3.3.7: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +nativewind@^4.1.23: + version "4.2.3" + resolved "https://registry.yarnpkg.com/nativewind/-/nativewind-4.2.3.tgz#ad7880bd2b5ac55f041d34918b9c67b00340088c" + integrity sha512-HglF1v6A8CqBFpXWs0d3yf4qQGurrreLuyE8FTRI/VDH8b0npZa2SDG5tviTkLiBg0s5j09mQALZOjxuocgMLA== + dependencies: + comment-json "^4.2.5" + debug "^4.3.7" + react-native-css-interop "0.2.3" + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + +nested-error-stacks@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz#d2cc9fc5235ddb371fc44d506234339c8e4b0a4b" + integrity sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A== + +node-forge@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2" + integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.36: + version "2.0.38" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.38.tgz#791569b9e4424a044e12c3abfad418ed83ce9947" + integrity sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-package-arg@^11.0.0: + version "11.0.3" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-11.0.3.tgz#dae0c21199a99feca39ee4bfb074df3adac87e2d" + integrity sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw== + dependencies: + hosted-git-info "^7.0.0" + proc-log "^4.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +ob1@0.83.3: + version "0.83.3" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.83.3.tgz#2208e20c9070e9beff3ad067f2db458fa6b07014" + integrity sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA== + dependencies: + flow-enums-runtime "^0.0.6" + +ob1@0.83.7: + version "0.83.7" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.83.7.tgz#0f9a9461ee3c3048eacd40893fee3385d42d8731" + integrity sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg== + dependencies: + flow-enums-runtime "^0.0.6" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-finished@~2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +open@^7.0.3: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +open@^8.0.4: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== + dependencies: + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-spinners "^2.0.0" + log-symbols "^2.2.0" + strip-ansi "^5.2.0" + wcwidth "^1.0.1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-png@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-png/-/parse-png-2.1.0.tgz#2a42ad719fedf90f81c59ebee7ae59b280d6b338" + integrity sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ== + dependencies: + pngjs "^3.3.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.5, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + +picomatch@^4.0.3, picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.1, pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +plist@^3.0.5: + version "3.1.1" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.1.tgz#fa6099e1e3cf6ea180258ebe6378ea3878c2c841" + integrity sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA== + dependencies: + "@xmldom/xmldom" "^0.9.10" + base64-js "^1.5.1" + xmlbuilder "^15.1.1" + +pngjs@^3.3.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6" + integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw== + dependencies: + camelcase-css "^2.0.1" + +"postcss-load-config@^4.0.2 || ^5.0 || ^6.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" + integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== + dependencies: + lilconfig "^3.1.1" + +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.47: + version "8.5.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.12.tgz#cd0c0f667f7cb0521e2313234ea6e707a9ec1ddb" + integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postcss@~8.4.32: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +proc-log@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034" + integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== + dependencies: + asap "~2.0.6" + +prompts@^2.3.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^7.2.5: + version "7.5.6" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.6.tgz#11af832ebc4b4326f658a5b1308e6141eb57edfd" + integrity sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.5" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.1" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.1" + "@types/node" ">=13.7.0" + long "^5.0.0" + +punycode@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qrcode-terminal@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz#ffc6c28a2fc0bfb47052b47e23f4f446a5fbdb9e" + integrity sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ== + +query-string@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +rc@~1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-devtools-core@^6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-6.1.5.tgz#c5eca79209dab853a03b2158c034c5166975feee" + integrity sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA== + dependencies: + shell-quote "^1.6.1" + ws "^7" + +react-freeze@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.4.tgz#cbbea2762b0368b05cbe407ddc9d518c57c6f3ad" + integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== + +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react-is@^19.1.0: + version "19.2.5" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.5.tgz#7e7b54143e9313fed787b23fd4295d5a23872ad9" + integrity sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ== + +react-native-css-interop@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/react-native-css-interop/-/react-native-css-interop-0.2.3.tgz#9efef674deffd8a0c70ddc9cedd1fc40c043e61b" + integrity sha512-wc+JI7iUfdFBqnE18HhMTtD0q9vkhuMczToA87UdHGWwMyxdT5sCcNy+i4KInPCE855IY0Ic8kLQqecAIBWz7w== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.3.7" + lightningcss "~1.27.0" + semver "^7.6.3" + +react-native-gesture-handler@~2.28.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz#07fb4f5eae72f810aac3019b060d26c1835bfd0c" + integrity sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A== + dependencies: + "@egjs/hammerjs" "^2.0.17" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + +react-native-is-edge-to-edge@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz#feb9a6a8faf0874298947edd556e5af22044e139" + integrity sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA== + +react-native-reanimated@~4.1.1: + version "4.1.7" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-4.1.7.tgz#b4e8524503a1b6ec1b5a40c460ee807a6a9fd2cf" + integrity sha512-Q4H6xA3Tn7QL0/E/KjI86I1KK4tcf+ErRE04LH34Etka2oVQhW6oXQ+Q8ZcDCVxiWp5vgbBH6XcH8BOo4w/Rhg== + dependencies: + react-native-is-edge-to-edge "^1.2.1" + semver "^7.7.2" + +react-native-safe-area-context@~5.6.0: + version "5.6.2" + resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz#283e006f5b434fb247fcb4be0971ad7473d5c560" + integrity sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg== + +react-native-screens@~4.16.0: + version "4.16.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.16.0.tgz#efa42e77a092aa0b5277c9ae41391ea0240e0870" + integrity sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q== + dependencies: + react-freeze "^1.0.0" + react-native-is-edge-to-edge "^1.2.1" + warn-once "^0.1.0" + +react-native-svg@15.12.1: + version "15.12.1" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.12.1.tgz#7ba756dd6a235f86a2c312a1e7911f9b0d18ad3a" + integrity sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g== + dependencies: + css-select "^5.1.0" + css-tree "^1.1.3" + warn-once "0.1.1" + +react-native-worklets@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.5.1.tgz#d153242655e3757b6c62a474768831157316ad33" + integrity sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w== + dependencies: + "@babel/plugin-transform-arrow-functions" "^7.0.0-0" + "@babel/plugin-transform-class-properties" "^7.0.0-0" + "@babel/plugin-transform-classes" "^7.0.0-0" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.0.0-0" + "@babel/plugin-transform-optional-chaining" "^7.0.0-0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0-0" + "@babel/plugin-transform-template-literals" "^7.0.0-0" + "@babel/plugin-transform-unicode-regex" "^7.0.0-0" + "@babel/preset-typescript" "^7.16.7" + convert-source-map "^2.0.0" + semver "7.7.2" + +react-native@0.81.5: + version "0.81.5" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.81.5.tgz#6c963f137d3979b22aef2d8482067775c8fe2fed" + integrity sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@react-native/assets-registry" "0.81.5" + "@react-native/codegen" "0.81.5" + "@react-native/community-cli-plugin" "0.81.5" + "@react-native/gradle-plugin" "0.81.5" + "@react-native/js-polyfills" "0.81.5" + "@react-native/normalize-colors" "0.81.5" + "@react-native/virtualized-lists" "0.81.5" + abort-controller "^3.0.0" + anser "^1.4.9" + ansi-regex "^5.0.0" + babel-jest "^29.7.0" + babel-plugin-syntax-hermes-parser "0.29.1" + base64-js "^1.5.1" + commander "^12.0.0" + flow-enums-runtime "^0.0.6" + glob "^7.1.1" + invariant "^2.2.4" + jest-environment-node "^29.7.0" + memoize-one "^5.0.0" + metro-runtime "^0.83.1" + metro-source-map "^0.83.1" + nullthrows "^1.1.1" + pretty-format "^29.7.0" + promise "^8.3.0" + react-devtools-core "^6.1.5" + react-refresh "^0.14.0" + regenerator-runtime "^0.13.2" + scheduler "0.26.0" + semver "^7.1.3" + stacktrace-parser "^0.1.10" + whatwg-fetch "^3.0.0" + ws "^6.2.3" + yargs "^17.6.2" + +react-refresh@^0.14.0, react-refresh@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" + integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== + +react@19.1.0: + version "19.1.0" + resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" + integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerate-unicode-properties@^10.2.2: + version "10.2.2" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" + integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regexpu-core@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.4.0.tgz#3580ce0c4faedef599eccb146612436b62a176e5" + integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.2" + regjsgen "^0.8.0" + regjsparser "^0.13.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.2.1" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.13.0: + version "0.13.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.1.tgz#0593cbacb27527927692030928ae4d3b878d6f8d" + integrity sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw== + dependencies: + jsesc "~3.1.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requireg@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/requireg/-/requireg-0.2.2.tgz#437e77a5316a54c9bcdbbf5d1f755fe093089830" + integrity sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg== + dependencies: + nested-error-stacks "~2.0.1" + rc "~1.2.7" + resolve "~1.7.1" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-workspace-root@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz#9cbbf8321ebccaaf0e4ffea5274aa26b611ccd62" + integrity sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w== + +resolve.exports@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.1.7, resolve@^1.22.11, resolve@^1.22.2, resolve@^1.22.8: + version "1.22.12" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" + integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== + dependencies: + es-errors "^1.3.0" + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@~1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw== + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.2.1, safe-buffer@>=5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sax@>=0.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.0.tgz#da59637629307b97e7c4cb28e080a7bc38560d5b" + integrity sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA== + +scheduler@0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" + integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== + +semver@7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.1.3, semver@^7.3.5, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.2: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +send@^0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "~0.5.2" + http-errors "~2.0.1" + mime "1.6.0" + ms "2.1.3" + on-finished "~2.4.1" + range-parser "~1.2.1" + statuses "~2.0.2" + +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== + +serve-static@^1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "~0.19.1" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sf-symbols-typescript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz#926d6e0715e3d8784cadf7658431e36581254208" + integrity sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1: + version "1.8.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== + +signal-exit@^3.0.2, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-plist@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" + integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== + dependencies: + bplist-creator "0.1.0" + bplist-parser "0.3.1" + plist "^3.0.5" + +simple-swizzle@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slugify@^1.3.4, slugify@^1.6.6: + version "1.6.9" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.9.tgz#610957dea21e56b65e3a153215ef7b265715c8e8" + integrity sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg== + +sonner-native@^0.21.0: + version "0.21.2" + resolved "https://registry.yarnpkg.com/sonner-native/-/sonner-native-0.21.2.tgz#dec1b214cf91e54f750737e784226651ba1d08f2" + integrity sha512-LnGPmfgzrNIwcc+FvcLJqx8aH1dEHePRzvNR8aIR4kl9spySRkXK160GmQIazjfm6mSMlPqZwRa5eycvrzg/eQ== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-support@~0.5.20, source-map-support@~0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-parser@^0.1.10: + version "0.1.11" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz#c7c08f9b29ef566b9a6f7b255d7db572f66fabc4" + integrity sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg== + dependencies: + type-fest "^0.7.1" + +statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +stream-buffers@2.2.x: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" + integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== + +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +structured-headers@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1" + integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg== + +sucrase@^3.35.0, sucrase@~3.35.1: + version "3.35.1" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwind-merge@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.5.0.tgz#06502f4496ba15151445d97d916a26564d50d1ca" + integrity sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A== + +tailwindcss@^3.4.17: + version "3.4.19" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.19.tgz#af2a0a4ae302d52ebe078b6775e799e132500ee2" + integrity sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.6.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.2" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.7" + lilconfig "^3.1.3" + micromatch "^4.0.8" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.47" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.2 || ^5.0 || ^6.0" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" + +tar@^7.5.2: + version "7.5.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" + integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" + +terminal-link@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser@^5.15.0: + version "5.46.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.2.tgz#b9529672d5b0024c7959571c83b82f65077b2a4f" + integrity sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.15.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +tinyglobby@^0.2.11: + version "0.2.16" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== + +typescript@~5.9.0: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~7.19.0: + version "7.19.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" + integrity sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== + +undici@^6.18.2: + version "6.25.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.25.0.tgz#8c4efb8c998dc187fc1cfb5dde1ef19a211849fb" + integrity sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz#65a7adfad8574c219890e219285ce4c64ed67eaa" + integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" + integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +use-latest-callback@^0.2.4: + version "0.2.6" + resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.6.tgz#e5ea752808c86219acc179ace0ae3c1203255e77" + integrity sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg== + +use-sync-external-store@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== + +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vlq@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" + integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== + +walker@^1.0.7, walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +warn-once@0.1.1, warn-once@^0.1.0, warn-once@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" + integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-fetch@^3.0.0: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + +whatwg-url-without-unicode@8.0.0-3: + version "8.0.0-3" + resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b" + integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig== + dependencies: + buffer "^5.4.3" + punycode "^2.1.1" + webidl-conversions "^5.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wonka@^6.3.2: + version "6.3.6" + resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.6.tgz#a70e2e54ed6aaa8e20bb57d916166cdc3d385f2d" + integrity sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.3.tgz#ccc96e4add5fd6fedbc491903075c85c5a11d9ee" + integrity sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA== + dependencies: + async-limiter "~1.0.0" + +ws@^7, ws@^7.5.10: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +ws@^8.12.1: + version "8.20.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.0.tgz#4cd9532358eba60bc863aad1623dfb045a4d4af8" + integrity sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA== + +xcode@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c" + integrity sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA== + dependencies: + simple-plist "^1.1.0" + uuid "^7.0.3" + +xml2js@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.0.tgz#07afc447a97d2bd6507a1f76eeadddb09f7a8282" + integrity sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + +yaml@^2.6.1: + version "2.8.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d" + integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.6.2, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" + integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== + +zustand@^5.0.11: + version "5.0.12" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.12.tgz#ed36f647aa89965c4019b671dfc23ef6c6e3af8c" + integrity sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==