diff --git a/.github/actions/setup-ci-deps/action.yml b/.github/actions/setup-ci-deps/action.yml index d53722f57dd75..7a6a1aa122a70 100644 --- a/.github/actions/setup-ci-deps/action.yml +++ b/.github/actions/setup-ci-deps/action.yml @@ -3,22 +3,38 @@ description: Install Vite+ and the workspace dependencies from cache. runs: using: composite steps: - # setup-vp reuses an installed exact version, so restoring its home skips the - # installer download on every job. + # ~/.vite-plus holds vp, the Node.js runtime, and pnpm. A cache entry never + # updates, so the key names all three versions; otherwise a pnpm or Node.js + # bump makes every job download them from npm again. - name: Resolve Vite+ version id: vp shell: bash run: | echo "version=$(sed -nE 's/^ vite-plus: ([0-9][^ ]*)$/\1/p' pnpm-workspace.yaml)" >> "$GITHUB_OUTPUT" + echo "node=$(jq -r '.engines.node' package.json)" >> "$GITHUB_OUTPUT" + echo "pnpm=$(jq -r '.packageManager' package.json)" >> "$GITHUB_OUTPUT" - name: Cache Vite+ + id: vp_cache if: steps.vp.outputs.version != '' uses: actions/cache@v6 with: path: ~/.vite-plus - key: vite-plus-home-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.vp.outputs.version }} + key: vite-plus-home-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.vp.outputs.version }}-node${{ steps.vp.outputs.node }}-${{ steps.vp.outputs.pnpm }} + + # setup-vp always runs the installer, which fetches vp from npm even when + # this exact version is installed. A cache hit already has everything, so + # skip setup-vp and do its remaining work here. This keeps npm outages from + # failing jobs. + - name: Use cached Vite+ + if: steps.vp_cache.outputs.cache-hit == 'true' + shell: bash + run: | + echo "$HOME/.vite-plus/bin" >> "$GITHUB_PATH" + "$HOME/.vite-plus/bin/vp" env use "${{ steps.vp.outputs.node }}" - name: Setup Vite+ + if: steps.vp_cache.outputs.cache-hit != 'true' uses: voidzero-dev/setup-vp@v1 with: node-version-file: package.json diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 65cfa08321c87..516a0a7785a95 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -253,18 +253,13 @@ jobs: if [[ "${{ inputs.version }}" =~ -nightly\.[0-9]{8}\.[0-9]+$ ]]; then brand=nightly; fi node scripts/apply-web-brand-assets.ts "$brand" apps/server/dist/client - # The single-executable is built with a Node that supports --build-sea - # (25.7+); the repo itself stays on the engines.node version. A native - # build injects into the runner's own Node; a Windows target downloads its - # node.exe. + # Setup Vite+ selects the exact engines.node version from package.json. + # Native builds embed that executable; cross-target builds download the + # same version for their target. - name: Build CLI single-executable if: inputs.cli_archive shell: bash env: - # The exact version, not a major: vp downloads it from nodejs.org/dist on - # the runner, and only exact versions have a dist directory. Keep in - # step with SEA_NODE_VERSION in apps/server/vite.config.ts. - VP_NODE_VERSION: "26.8.2" # The executable bundles the server again, so it needs the same stamp # as release.yml's JS bundle. T3CODE_TELEMETRY_CHANNEL: ${{ github.repository == 'pingdotgg/t3code' && inputs.release_channel || 'dev' }} diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index ff75832369d8d..e79d0e71dc863 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -5,6 +5,15 @@ // Release scripts stage their own installs with explicit architectures. module.exports = { hooks: { + // `typescript` is @tsc-rs/typescript (the catalog entry). Its npm version (0.8.x) is outside + // vite-plus-core's typescript peer range, so pnpm gave the vite-plus that @effect/vitest + // imports apps/marketing's typescript@6.0.3 instead, and tests loaded a second vitest. + readPackage(pkg) { + if (pkg.name === "@voidzero-dev/vite-plus-core" && pkg.peerDependencies?.typescript) { + pkg.peerDependencies.typescript = "*"; + } + return pkg; + }, updateConfig(config) { // oxlint-disable-next-line t3code/no-global-process-runtime -- pnpm loads this file before any Effect runtime exists. if (process.platform !== "win32") return config; diff --git a/AGENTS.md b/AGENTS.md index 9bcafc5cceb2b..1bb59f2a8d6fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,7 +100,7 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real - Before you push, run `vp run check:changed`. It runs CI's fast checks for changed files: format, lint, knip, typecheck of affected packages and dependents, and tests beside changes. CI still runs the full test suites. - While you iterate, run `vp test run `. For big files, add `-t ""`. -- Do not use `npx tsc`. Use `vp run typecheck`; it uses tsc-rs, which is 3x faster. +- Use `vp run typecheck`, not `npx tsc`; it caches per package. Both run tsc-rs (`typescript` is the `@tsc-rs/typescript` drop-in). - Run `node -e` snippets from `apps/server` or the owning package. `effect` does not resolve from the repo root. - Test meaningful logic or observable behavior. Do not render components to static markup to assert props or attributes, or add tests that merely assert callback wiring or mirror the implementation. - **Do not run repo-wide checks.** No `vp check`, no `vp run -r test`, no `vp run -r typecheck` unless I ask. CI owns the full suite. diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index c0a77e9a48b77..4404dddbaa3f5 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -43,9 +43,9 @@ export default defineConfig({ dependsOn: ["t3#build"], cache: false, }, - // tsc-rs rewrites the tsbuildinfo it reads, which keeps a task out of the cache. + // tsc (tsc-rs) rewrites the tsbuildinfo it reads, which keeps a task out of the cache. typecheck: { - command: "tsc-rs --noEmit", + command: "tsc --noEmit", cache: { input: [{ auto: true }, "!*.tsbuildinfo"] }, }, }, diff --git a/apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz b/apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz deleted file mode 100644 index 581307cf91be1..0000000000000 Binary files a/apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz and /dev/null differ diff --git a/apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz b/apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz new file mode 100644 index 0000000000000..071a4022a7b3f Binary files /dev/null and b/apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz differ diff --git a/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift b/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift index 9a5dae1db3f20..919902b051ca8 100644 --- a/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift +++ b/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift @@ -13,7 +13,7 @@ private struct ComposerTokenPayload: Decodable { let end: Int } -private struct ComposerSelectionPayload: Decodable { +private struct ComposerSelectionPayload: Decodable, Equatable { let start: Int let end: Int } @@ -449,6 +449,9 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro private var tokensNeedRebuild = false private var chipsNeedMeasuredWidth = false private var hasDeferredForcedRebuild = false + private var pendingContentOffset: CGPoint? + private var needsSelectionReveal = false + private var selectionBeforeFocusingTap: (selection: ComposerSelectionPayload, value: String)? let onComposerChange = EventDispatcher() let onComposerSelectionChange = EventDispatcher() @@ -464,7 +467,8 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro public required init(appContext: AppContext? = nil) { super.init(appContext: appContext) - clipsToBounds = false + clipsToBounds = true + textView.clipsToBounds = true textView.delegate = self textView.textDropDelegate = self textView.backgroundColor = .clear @@ -520,6 +524,12 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro @objc private func openContext(_ recognizer: UITapGestureRecognizer) { guard let (index, attachment) = contextAttachment(at: recognizer.location(in: textView)) else { textView.becomeFirstResponder() + if selectionBeforeFocusingTap != nil { + // UIKit places the caret from its own tap recognizer during this same touch. + DispatchQueue.main.async { [weak self] in + self?.restoreSelectionBeforeFocusingTap() + } + } return } openContext(index: index, attachment: attachment) @@ -558,7 +568,11 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { - true + // The collapsed editor only shows the draft's first line, so a tap that expands an + // overflowing draft returns to the caret it was left at instead of moving it there. + let collapsedOverflow = !textView.isFirstResponder && !textView.isScrollEnabled && draftOverflowsBounds() + selectionBeforeFocusingTap = collapsedOverflow ? (sourceSelection(), value) : nil + return true } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { @@ -583,11 +597,27 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro public override func layoutSubviews() { super.layoutSubviews() + let previousSize = textView.bounds.size textView.frame = bounds if chipsNeedMeasuredWidth, bounds.width > 0 { chipsNeedMeasuredWidth = false applyControlledDocument(force: true) } + if textView.isScrollEnabled, bounds.width > 0, bounds.height > 0 { + textView.layoutIfNeeded() + if let offset = pendingContentOffset { + let minY = -textView.adjustedContentInset.top + let maxY = max(minY, textView.contentSize.height - bounds.height + textView.adjustedContentInset.bottom) + textView.setContentOffset(CGPoint(x: offset.x, y: min(maxY, max(minY, offset.y))), animated: false) + pendingContentOffset = nil + } + if needsSelectionReveal || (previousSize != bounds.size && textView.isFirstResponder) { + needsSelectionReveal = false + if let selection = textView.selectedTextRange { + textView.scrollRectToVisible(textView.caretRect(for: selection.end), animated: false) + } + } + } let placeholderX = textView.textContainerInset.left + textView.textContainer.lineFragmentPadding let placeholderY = textView.textContainerInset.top let placeholderWidth = max( @@ -632,10 +662,12 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro tokens = decode([ComposerTokenPayload].self, from: document.tokensJson) ?? [] tokensNeedRebuild = true } + let previousSelection = sourceSelection() value = document.value requestedSelection = document.selection applyControlledDocument(force: tokensNeedRebuild) applyRequestedSelection() + cancelFocusingTapRestoreIfSelectionMoved(from: previousSelection) if tokensMatchCurrentValue() { tokensNeedRebuild = false } @@ -694,7 +726,16 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro } func setScrollEnabled(_ scrollEnabled: Bool) { + guard textView.isScrollEnabled != scrollEnabled else { return } + if !scrollEnabled { + // Disabling scrolling discards UITextView's offset. Restore it after + // expansion has laid out the text, regardless of prop update order. + pendingContentOffset = pendingContentOffset ?? textView.contentOffset + } textView.isScrollEnabled = scrollEnabled + if scrollEnabled { + revealSelectionAfterLayout() + } } func setAutoFocus(_ autoFocus: Bool) { @@ -738,8 +779,10 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro } func setSelection(start: Int, end: Int) { + let previousSelection = sourceSelection() requestedSelection = ComposerSelectionPayload(start: start, end: end) applyRequestedSelection() + cancelFocusingTapRestoreIfSelectionMoved(from: previousSelection) } public func textViewDidChange(_ textView: UITextView) { @@ -811,6 +854,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro } public func textViewDidBeginEditing(_ textView: UITextView) { + revealSelectionAfterLayout() onComposerFocus() } @@ -836,6 +880,9 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro hasDeferredForcedRebuild = false let previousSelection = sourceSelection() + if textView.isScrollEnabled { + pendingContentOffset = pendingContentOffset ?? textView.contentOffset + } isApplyingControlledValue = true textView.attributedText = makeAttributedDocument() let targetSelection = requestedSelection ?? previousSelection @@ -843,6 +890,11 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro textView.selectedRange = displayRange(for: targetSelection) restoreBaseTypingAttributes() isApplyingControlledValue = false + if currentSource != value || previousSelection.start != targetSelection.start || previousSelection.end != targetSelection.end { + revealSelectionAfterLayout() + } else { + setNeedsLayout() + } updatePlaceholderVisibility() emitContentSizeIfNeeded() } @@ -1053,6 +1105,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro return } value = textView.serializedText() + revealSelectionAfterLayout() let selection = sourceSelection() nativeEventCount += 1 onComposerChange([ @@ -1122,6 +1175,40 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro isApplyingControlledValue = true textView.selectedRange = nextRange isApplyingControlledValue = false + revealSelectionAfterLayout() + } + + private func restoreSelectionBeforeFocusingTap() { + guard let snapshot = selectionBeforeFocusingTap else { + return + } + selectionBeforeFocusingTap = nil + // A draft that changed during the touch, such as a transcript landing, owns the caret now. + guard textView.isFirstResponder, textView.markedTextRange == nil, value == snapshot.value else { + return + } + let range = displayRange(for: snapshot.selection) + guard !NSEqualRanges(range, textView.selectedRange) else { + return + } + textView.selectedRange = range + revealSelectionAfterLayout() + } + + private func cancelFocusingTapRestoreIfSelectionMoved(from previousSelection: ComposerSelectionPayload) { + if sourceSelection() != previousSelection { + selectionBeforeFocusingTap = nil + } + } + + private func draftOverflowsBounds() -> Bool { + let size = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .greatestFiniteMagnitude)) + return size.height > textView.bounds.height + 1 + } + + private func revealSelectionAfterLayout() { + needsSelectionReveal = true + setNeedsLayout() } private func updatePlaceholderVisibility() { @@ -1129,28 +1216,16 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro } private func emitContentSizeIfNeeded() { - // The laid-out text without insets. A one-line draft reports its natural width, so - // JS can tell whether it fits a narrower row than the one it is laid out in; past - // one line only the height matters, so layout stops at the second line. The width - // comes from the line fragment because usedRect spans the container once the view - // scrolls. A trailing newline's empty line only exists as the extra fragment. - let layout = textView.layoutManager - let container = textView.textContainer - var lines = 0 - var width: CGFloat = 0 - var height: CGFloat = 0 - let glyphs = NSRange(location: 0, length: layout.numberOfGlyphs) - layout.enumerateLineFragments(forGlyphRange: glyphs) { _, used, _, _, stop in - lines += 1 - width = used.width - height = used.maxY - if lines > 1 { stop.pointee = true } - } - if lines < 2, layout.extraLineFragmentTextContainer === container { - lines += 1 - height = max(height, layout.extraLineFragmentUsedRect.maxY) - } - let nextSize = CGSize(width: lines == 1 ? ceil(width) : 0, height: ceil(height)) + guard bounds.width > 0 else { return } + // Fit independently of the animated viewport: when scrolling is disabled, + // its current height can clip TextKit's laid-out line fragments. + let fittedHeight = textView.sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude)).height + - textView.textContainerInset.top - textView.textContainerInset.bottom + let measuredHeight = min(lineHeight * 4, fittedHeight) + let width = fittedHeight <= lineHeight + 0.5 + ? textView.layoutManager.usedRect(for: textView.textContainer).width + : 0 + let nextSize = CGSize(width: ceil(width), height: ceil(measuredHeight)) guard abs(nextSize.width - lastContentSize.width) > 0.5 || abs(nextSize.height - lastContentSize.height) > 0.5 else { return diff --git a/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request.png b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request.png new file mode 100644 index 0000000000000..7443f8e0096dc Binary files /dev/null and b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request.png differ diff --git a/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@2x.png b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@2x.png new file mode 100644 index 0000000000000..efc4cb3a093d5 Binary files /dev/null and b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@2x.png differ diff --git a/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@3x.png b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@3x.png new file mode 100644 index 0000000000000..ee63420dba56d Binary files /dev/null and b/apps/mobile/modules/t3-native-controls/ios/Resources/Icons/pull-request@3x.png differ diff --git a/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift b/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift index 936efe7676e95..b970936b1fe8f 100644 --- a/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift +++ b/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift @@ -69,6 +69,7 @@ final class T3LayoutMetricsView: ExpoView { } var verticalBarEdge = "none" var regions: [[String: Any]] = [] + var bottomBarInset: CGFloat? #if canImport(UIKit, _version: 9127.0.85) if #available(iOS 27.1, *) { let rtl = effectiveUserInterfaceLayoutDirection == .rightToLeft @@ -77,6 +78,8 @@ final class T3LayoutMetricsView: ExpoView { case .trailing: verticalBarEdge = rtl ? "left" : "right" default: break } + // Where UIKit floats a bar as tall as the app's bottom controls, level with its tab bar. + bottomBarInset = edgeInsets(for: .bar(onEdge: UIRectEdge.bottom, extent: 48)).bottom for kind in [UIView.ReservedRegion.Kind.division, .occlusion] { for region in reservedRegions(kind: kind) where region.isActive { let frame = region.frame.intersection(bounds) @@ -90,7 +93,7 @@ final class T3LayoutMetricsView: ExpoView { } } #endif - let metrics: [String: Any] = [ + var metrics: [String: Any] = [ "width": bounds.width, "height": bounds.height, "hasHinge": hasHinge, "horizontalSizeClass": traitCollection.horizontalSizeClass == .regular ? "regular" : "compact", @@ -101,6 +104,7 @@ final class T3LayoutMetricsView: ExpoView { ], "reservedRegions": regions, ] + metrics["bottomBarInset"] = bottomBarInset let next = metrics as NSDictionary guard lastMetrics?.isEqual(next) != true else { return } lastMetrics = next diff --git a/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec b/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec index 29735c301cc0e..c32f88b0dacb3 100644 --- a/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec +++ b/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec @@ -15,5 +15,6 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', } + s.resource_bundles = { 'T3NativeControlIcons' => ['Resources/Icons/pull-request*.png'] } s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}' end diff --git a/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift b/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift index 479b5fed0ce51..81a892c8becf8 100644 --- a/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift +++ b/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift @@ -32,6 +32,14 @@ public final class T3NativeControlsModule: Module { Prop("appearance") { (view: T3SegmentedControlView, value: String) in view.appearance = value } } + View(T3SidebarTabBarView.self) { + ViewName("SidebarTabBar") + Events("onSelectionChange") + Prop("selectedIndex") { (view: T3SidebarTabBarView, value: Int) in view.selectedIndex = value } + Prop("selectedColor") { (view: T3SidebarTabBarView, value: UIColor) in view.selectedColor = value } + Prop("appearance") { (view: T3SidebarTabBarView, value: String) in view.appearance = value } + } + View(T3LayoutMetricsView.self) { ViewName("LayoutMetrics") Events("onMetricsChange") diff --git a/apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift b/apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift new file mode 100644 index 0000000000000..3bfd730facd17 --- /dev/null +++ b/apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift @@ -0,0 +1,60 @@ +import ExpoModulesCore +import UIKit + +final class T3SidebarTabBarView: ExpoView, UITabBarDelegate { + let onSelectionChange = EventDispatcher() + private let tabBar = UITabBar() + var selectedIndex = 0 { didSet { updateSelection() } } + var selectedColor: UIColor = .systemBlue { didSet { tabBar.tintColor = selectedColor } } + var appearance = "light" { + didSet { overrideUserInterfaceStyle = appearance == "dark" ? .dark : .light } + } + + required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + let iconBundle = Bundle(for: Self.self).url(forResource: "T3NativeControlIcons", withExtension: "bundle").flatMap(Bundle.init(url:)) + let pullRequestIcon = UIImage(named: "pull-request", in: iconBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + tabBar.items = [ + UITabBarItem(title: "Threads", image: tabIcon(UIImage(systemName: "text.bubble")), tag: 0), + UITabBarItem(title: "Pulls", image: tabIcon(pullRequestIcon), tag: 1), + ] + tabBar.items?.last?.accessibilityLabel = "Pull requests" + tabBar.items?.first?.selectedImage = tabIcon(UIImage(systemName: "text.bubble.fill")) + tabBar.itemPositioning = .fill + tabBar.delegate = self + addSubview(tabBar) + updateSelection() + } + + override func layoutSubviews() { + super.layoutSubviews() + tabBar.frame = bounds + updateSelection() + } + + override func didMoveToWindow() { + super.didMoveToWindow() + updateSelection() + } + + // Give symbols and bitmap assets the same image box; UIKit otherwise scales symbols independently. + private func tabIcon(_ image: UIImage?) -> UIImage? { + guard let image else { return nil } + let canvas = CGSize(width: 28, height: 28) + let scale = min(24 / image.size.width, 24 / image.size.height) + let size = CGSize(width: image.size.width * scale, height: image.size.height * scale) + return UIGraphicsImageRenderer(size: canvas).image { _ in + image.draw(in: CGRect(x: (canvas.width - size.width) / 2, y: (canvas.height - size.height) / 2, width: size.width, height: size.height)) + }.withRenderingMode(.alwaysTemplate) + } + + private func updateSelection() { + guard let items = tabBar.items, items.indices.contains(selectedIndex) else { return } + guard tabBar.selectedItem !== items[selectedIndex] else { return } + tabBar.selectedItem = items[selectedIndex] + } + + func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { + onSelectionChange(["index": item.tag]) + } +} diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 441ee7beb6d35..9f1a944485088 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -45,7 +45,7 @@ "measure-renders": "node scripts/measure-renders.mts", "generate": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/generate-uniwind-themes.mts", "test": "vp test run --project unit --project react", - "typecheck": "tsc-rs --noEmit" + "typecheck": "tsc --noEmit" }, "dependencies": { "@clerk/expo": "catalog:", @@ -134,7 +134,7 @@ "react-native-pdf-jsi": "5.0.0", "react-native-reanimated": "4.7.1", "react-native-safe-area-context": "~5.10.1", - "react-native-screens": "file:deps/react-native-screens-5.0.0-t3.30.tgz", + "react-native-screens": "file:deps/react-native-screens-5.0.0-t3.36.tgz", "react-native-shiki-engine": "^0.3.12", "react-native-svg": "15.16.0", "react-native-webview": "14.0.1", diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index bfe5d1774a6bf..819f780ed7d3a 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -6,7 +6,7 @@ import { StatusBar } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { KeyboardProvider } from "react-native-keyboard-controller"; import { SafeAreaProvider } from "react-native-safe-area-context"; -import { createStaticNavigation } from "@react-navigation/native"; +import { createStaticNavigation, getStateFromPath } from "@react-navigation/native"; import { RegistryContext } from "@effect/atom-react"; import { ThreadArrangementHost } from "./features/threads/ThreadArrangementSheet"; @@ -24,7 +24,7 @@ import { subscribeComposerDraftModelReconciliation } from "./state/use-composer- import { environmentThreadShells } from "./state/threads"; import { environmentShell } from "./state/shell"; import { OverlayPortalHost } from "./components/ui/OverlayPortal"; -import { shouldHandleAppLink } from "./lib/appLinking"; +import { shouldHandleAppLink, restoreDraftLinkState } from "./lib/appLinking"; import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme"; import { useUiRuntimeMemoryWarningGc } from "./lib/useUiRuntimeMemoryWarningGc"; import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator"; @@ -44,6 +44,8 @@ const appLinking = { prefixes: [Linking.createURL("/"), "t3code://", "t3code-dev://", "t3code-preview://"], config: { initialRouteName: "Home" }, filter: shouldHandleAppLink, + getStateFromPath: (path: string, options: Parameters[1]) => + restoreDraftLinkState(getStateFromPath(path, options), path), }; const Navigation = createStaticNavigation(RootStack); diff --git a/apps/mobile/src/Stack.tsx b/apps/mobile/src/Stack.tsx index 13240c54a975f..d5a8a038fe435 100644 --- a/apps/mobile/src/Stack.tsx +++ b/apps/mobile/src/Stack.tsx @@ -1,11 +1,9 @@ +import { isDraftThreadRoute } from "./features/threads/thread-route-state"; import { ComponentCatalogScreen } from "./features/development/ComponentCatalogScreen"; import { PullRequestGuideStopScreen } from "./features/pull-requests/guide/PullRequestGuideScreen"; import { PullRequestInlineCommentScreen } from "./features/pull-requests/PullRequestInlineCommentScreen"; import { PullRequestDiscussionScreen } from "./features/pull-requests/PullRequestDiscussionScreen"; -import { - createV5StackNavigator as createNativeStackNavigator, - createV5SheetStackNavigator, -} from "./native/createV5StackNavigator"; +import { createV5SheetStackNavigator } from "./native/createV5StackNavigator"; import { createWorkspaceStackNavigator } from "./features/layout/createWorkspaceStackNavigator"; import { createPathConfigForStaticNavigation, @@ -19,14 +17,7 @@ import { type NativeStackNavigationOptions, } from "@react-navigation/native-stack"; import { useEffect, useRef, type ReactNode } from "react"; -import { - Platform, - Pressable, - ScrollView, - StyleSheet, - View, - useWindowDimensions, -} from "react-native"; +import { Platform, Pressable, ScrollView, StyleSheet, useWindowDimensions } from "react-native"; import { useResolveClassNames } from "uniwind"; import { AppText as Text } from "./components/ui/AppText"; @@ -82,7 +73,6 @@ import { AddProjectLocalRoute } from "./features/projects/AddProjectLocalRoute"; import { AddProjectNewRoute } from "./features/projects/AddProjectNewRoute"; import { AddProjectRepositoryRoute } from "./features/projects/AddProjectRepositoryRoute"; import { AddProjectSourceRoute } from "./features/projects/AddProjectSourceRoute"; -import { NewTaskDraftRouteScreen } from "./features/threads/NewTaskDraftRouteScreen"; import { NewTaskBranchPickerRouteScreen, NewTaskEnvironmentPickerRouteScreen, @@ -149,7 +139,7 @@ import { NATIVE_LIQUID_GLASS_SUPPORTED } from "./native/native-glass"; import { deriveLayout } from "./lib/layout"; import { useNativeLayoutMetrics } from "./native/native-layout-metrics"; import { nativeHeaderScrollEdgeEffects } from "./native/StackHeader"; -import { FORM_SHEET_PRESENTATION_OPTIONS } from "./native/sheet-surface"; +import { HORIZONTAL_FORM_SHEET_OPTIONS, formSheetDetents } from "./native/sheet-surface"; import { useThreadOutboxDrain } from "./state/use-thread-outbox-drain"; import { useComposerAttachmentUploadWorker } from "./state/composer-attachment-uploads"; @@ -210,6 +200,15 @@ const LEGAL_DOCUMENT_HEADER_OPTIONS: AppScreenOptions = { const NESTED_NAVIGATOR_OPTIONS = { nativeContentInsetHorizontally: false, headerShown: false }; +// Thread settings keep the Duo's vertical bar so cancel, search, filter and confirm sit in the +// rail. The picker stack spans the sheet so its header reaches the rail, and insets its content. +const THREAD_SETTINGS_SHEET_OPTIONS = { + presentation: "formSheet" as const, + sheetAllowedDetents: [1], + sheetGrabberVisible: true, + nativeContentInsetHorizontally: false, +}; + const SettingsContentStack = createV5SheetStackNavigator({ initialRouteName: "Settings", screenOptions: { @@ -368,9 +367,7 @@ const SettingsContentStack = createV5SheetStackNavigator({ ...(Platform.OS === "android" ? { presentation: "card" as const } : { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [1], - sheetGrabberVisible: true, + ...THREAD_SETTINGS_SHEET_OPTIONS, }), }, }), @@ -454,97 +451,6 @@ const SettingsSheetStack = createV5SheetStackNavigator({ // across two bars the whole screen slides). const THREAD_LINKING_PREFIX = "threads/:environmentId/:threadId"; -const NewTaskSheetStack = createNativeStackNavigator({ - initialRouteName: "NewTask", - screenOptions: { - ...SHEET_GLASS_HEADER_OPTIONS, - contentStyle: Platform.OS === "ios" ? { backgroundColor: "transparent" } : undefined, - // UIKit's default push adds a dimming shadow and independently transitions - // the navigation bar. Both read as mismatched sheet backgrounds here. - animation: Platform.OS === "ios" ? "simple_push" : undefined, - animationDuration: Platform.OS === "ios" ? 350 : undefined, - }, - screens: { - NewTask: createNativeStackScreen({ - screen: NewTaskRouteScreen, - linking: "", - options: { - title: "Choose project", - }, - }), - NewTaskDraft: createNativeStackScreen({ - screen: NewTaskDraftRouteScreen, - linking: "draft", - options: { - headerBackVisible: false, - title: "", - }, - }), - NewTaskEnvironment: createNativeStackScreen({ - screen: NewTaskEnvironmentPickerRouteScreen, - linking: "draft/environment", - options: { - title: "Environment", - }, - }), - NewTaskBranch: createNativeStackScreen({ - screen: NewTaskBranchPickerRouteScreen, - linking: "draft/branch", - options: { - title: "Branch", - }, - }), - NewTaskFile: createNativeStackScreen({ - screen: ThreadFileScreen, - linking: "draft/files/:path*", - options: SOLID_HEADER_OPTIONS, - }), - NewTaskAttachment: createNativeStackScreen({ - screen: AttachmentFileScreen, - linking: "draft/attachments/:attachmentId", - options: SOLID_HEADER_OPTIONS, - }), - ThreadSettings: createNativeStackScreen({ - screen: NewTaskThreadSettingsRouteScreen, - linking: "draft/settings", - options: { - gestureEnabled: true, - headerShown: false, - ...(Platform.OS === "android" - ? { presentation: "card" as const } - : { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [1], - sheetGrabberVisible: true, - }), - }, - }), - AddProject: createNativeStackScreen({ - screen: AddProjectSourceRoute, - linking: "add-project", - options: { - title: "Add Project", - }, - }), - AddProjectRepository: createNativeStackScreen({ - screen: AddProjectRepositoryRoute, - linking: "add-project/repository", - }), - AddProjectDestination: createNativeStackScreen({ - screen: AddProjectDestinationRoute, - linking: "add-project/destination", - }), - AddProjectLocal: createNativeStackScreen({ - screen: AddProjectLocalRoute, - linking: "add-project/local", - }), - AddProjectNew: createNativeStackScreen({ - screen: AddProjectNewRoute, - linking: "add-project/new", - }), - }, -}); - const WORKSPACE_OVERLAY_ROUTES = new Set([ "PullRequestReview", "PullRequestInlineComment", @@ -555,7 +461,18 @@ const WORKSPACE_OVERLAY_ROUTES = new Set([ "GitCommit", "GitConfirm", "GitOverview", - "NewTaskSheet", + "NewTaskProject", + "NewTaskFiles", + "NewTaskFile", + "NewTaskAttachment", + "NewTaskThreadSettings", + "AddProject", + "AddProjectRepository", + "AddProjectDestination", + "AddProjectLocal", + "AddProjectNew", + "NewTaskEnvironment", + "NewTaskBranch", "SettingsLegal", "SettingsSheet", "ThreadAgents", @@ -606,44 +523,58 @@ function RootStackLayout(props: { const path = getPathFromState(props.state, navigationPathConfig); const pathname = path.startsWith("/") ? path : `/${path}`; const workspaceLocation = workspaceLocationFromState(props.state); + const draftRoute = props.state.routes.findLast((route) => route.name === "Thread"); + const draftParams = ( + draftRoute?.params as + | { draft?: { draftId?: string | string[]; pendingTaskId?: string | string[] } } + | undefined + )?.draft; + const routeDraftId = Array.isArray(draftParams?.draftId) + ? draftParams.draftId[0] + : draftParams?.draftId; + const routePendingTaskId = Array.isArray(draftParams?.pendingTaskId) + ? draftParams.pendingTaskId[0] + : draftParams?.pendingTaskId; useAgentNotificationNavigation(workspaceLocation.pathname); useConnectOnboardingNavigation(); useAppShortcuts(props.state); useEffect(() => { - const topRouteName = props.state.routes[props.state.index]?.name; const transition = transitionIncomingSharePresentation(sharePresentationRef.current, { - isShareSheetPresented: topRouteName === "NewTaskSheet", + isShareSheetPresented: isDraftThreadRoute(props.state.routes[props.state.index]), pendingShareId: pendingShare?.id ?? null, }); sharePresentationRef.current = transition.state; if (!transition.shareIdToPresent) { return; } - navigation.navigate("NewTaskSheet", { - screen: "NewTask", - params: { incomingShareId: transition.shareIdToPresent }, - }); + navigation.navigate("Thread", { draft: { incomingShareId: transition.shareIdToPresent } }); }, [navigation, pendingShare, props.state]); return ( - - - - - - - - {props.children} - - - - - - + + + + + + + + + {props.children} + + + + + + + ); } @@ -734,8 +665,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: PullRequestFilesSheet, options: { ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.6, 0.92], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.6), sheetGrabberVisible: true, sheetCornerRadius: Platform.OS === "android" ? 28 : undefined, }, @@ -744,8 +675,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: PullRequestReviewersScreen, options: { ...DETAIL_HEADER_OPTIONS, - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.75, 0.92], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.75), sheetGrabberVisible: true, headerBackVisible: false, }, @@ -759,8 +690,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ options: { ...(Platform.OS === "android" ? { presentation: "fullScreenModal" as const } - : { ...SHEET_GLASS_HEADER_OPTIONS, ...FORM_SHEET_PRESENTATION_OPTIONS }), - sheetAllowedDetents: Platform.OS === "android" ? undefined : [0.75, 0.92], + : { ...SHEET_GLASS_HEADER_OPTIONS, ...HORIZONTAL_FORM_SHEET_OPTIONS }), + sheetAllowedDetents: Platform.OS === "android" ? undefined : formSheetDetents(0.75), sheetGrabberVisible: Platform.OS !== "android", }, }), @@ -768,16 +699,86 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: PullRequestReviewSheet, options: { ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.75, 0.92], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.75), sheetGrabberVisible: true, }, }), Thread: createNativeStackScreen({ screen: ThreadRouteScreen, - linking: THREAD_LINKING_PREFIX, + linking: { path: "threads/:environmentId?/:threadId?", alias: ["new/draft"] }, options: DETAIL_HEADER_OPTIONS, }), + NewTaskProject: createNativeStackScreen({ + screen: NewTaskRouteScreen, + linking: { path: "new", alias: ["new/project"] }, + options: { ...GLASS_HEADER_OPTIONS, title: "Choose project" }, + }), + NewTaskEnvironment: createNativeStackScreen({ + linking: "new/draft/environment", + screen: NewTaskEnvironmentPickerRouteScreen, + options: { ...GLASS_HEADER_OPTIONS, title: "Environment" }, + }), + NewTaskBranch: createNativeStackScreen({ + linking: "new/draft/branch", + screen: NewTaskBranchPickerRouteScreen, + options: { ...GLASS_HEADER_OPTIONS, title: "Branch" }, + }), + AddProject: createNativeStackScreen({ + linking: "new/add-project", + screen: AddProjectSourceRoute, + + options: { + ...GLASS_HEADER_OPTIONS, + title: "Add project", + }, + }), + AddProjectRepository: createNativeStackScreen({ + linking: "new/add-project/repository", + screen: AddProjectRepositoryRoute, + options: GLASS_HEADER_OPTIONS, + }), + AddProjectDestination: createNativeStackScreen({ + linking: "new/add-project/destination", + screen: AddProjectDestinationRoute, + options: GLASS_HEADER_OPTIONS, + }), + AddProjectLocal: createNativeStackScreen({ + linking: "new/add-project/local", + screen: AddProjectLocalRoute, + options: GLASS_HEADER_OPTIONS, + }), + AddProjectNew: createNativeStackScreen({ + linking: "new/add-project/new", + screen: AddProjectNewRoute, + options: GLASS_HEADER_OPTIONS, + }), + NewTaskFiles: createNativeStackScreen({ + linking: "new/draft/files", + screen: ThreadFilesTreeScreen, + options: SOLID_HEADER_OPTIONS, + }), + NewTaskFile: createNativeStackScreen({ + linking: "new/draft/files/:path*", + screen: ThreadFileScreen, + options: SOLID_HEADER_OPTIONS, + }), + NewTaskAttachment: createNativeStackScreen({ + screen: AttachmentFileScreen, + linking: "new/draft/attachments/:attachmentId", + options: SOLID_HEADER_OPTIONS, + }), + NewTaskThreadSettings: createNativeStackScreen({ + screen: NewTaskThreadSettingsRouteScreen, + linking: "new/draft/settings", + options: { + gestureEnabled: true, + headerShown: false, + ...(Platform.OS === "android" + ? { presentation: "card" as const } + : THREAD_SETTINGS_SHEET_OPTIONS), + }, + }), ThreadTerminal: createNativeStackScreen({ screen: ThreadTerminalRouteScreen, linking: `${THREAD_LINKING_PREFIX}/terminal`, @@ -816,8 +817,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ // formSheet; use a full-screen modal there instead. ...(Platform.OS === "android" ? { presentation: "fullScreenModal" as const } - : FORM_SHEET_PRESENTATION_OPTIONS), - sheetAllowedDetents: Platform.OS === "android" ? undefined : [0.55, 0.92], + : HORIZONTAL_FORM_SHEET_OPTIONS), + sheetAllowedDetents: Platform.OS === "android" ? undefined : formSheetDetents(0.55), sheetGrabberVisible: Platform.OS !== "android", }, }), @@ -856,36 +857,34 @@ const RootStackConfig = createWorkspaceStackNavigator({ ...(Platform.OS === "android" ? { presentation: "card" as const } : { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [1], - sheetGrabberVisible: true, + ...THREAD_SETTINGS_SHEET_OPTIONS, }), }, }), ThreadQueue: createNativeStackScreen({ screen: ThreadQueueSheet, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, + ...HORIZONTAL_FORM_SHEET_OPTIONS, headerShown: false, - sheetAllowedDetents: [0.65, 0.95], + sheetAllowedDetents: formSheetDetents(0.65, 0.95), sheetGrabberVisible: true, }, }), ThreadBackgroundWork: createNativeStackScreen({ screen: ThreadBackgroundWorkSheet, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, + ...HORIZONTAL_FORM_SHEET_OPTIONS, headerShown: false, - sheetAllowedDetents: [0.5, 0.9], + sheetAllowedDetents: formSheetDetents(0.5, 0.9), sheetGrabberVisible: true, }, }), ThreadAgents: createNativeStackScreen({ screen: ThreadAgentsSheet, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, + ...HORIZONTAL_FORM_SHEET_OPTIONS, headerShown: false, - sheetAllowedDetents: [0.5, 0.9], + sheetAllowedDetents: formSheetDetents(0.5, 0.9), sheetGrabberVisible: true, }, }), @@ -893,8 +892,9 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: GitOverviewSheet, linking: `${THREAD_LINKING_PREFIX}/git`, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.55, 0.92], + ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.55), sheetGrabberVisible: true, }, }), @@ -902,8 +902,9 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: ThreadPullRequestsSheet, linking: `${THREAD_LINKING_PREFIX}/pull-requests`, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.55, 0.92], + ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.55), sheetGrabberVisible: true, }, }), @@ -912,8 +913,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ linking: `${THREAD_LINKING_PREFIX}/git/commit`, options: { ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.8, 0.92], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.8), sheetGrabberVisible: true, }, }), @@ -921,8 +922,9 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: GitBranchesSheet, linking: `${THREAD_LINKING_PREFIX}/git/branches`, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.55, 0.92], + ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.55), sheetGrabberVisible: true, }, }), @@ -930,8 +932,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ screen: GitConfirmSheet, linking: `${THREAD_LINKING_PREFIX}/git-confirm`, options: { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.45, 0.7], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: [0.55, 0.7], sheetGrabberVisible: true, }, }), @@ -964,8 +966,8 @@ const RootStackConfig = createWorkspaceStackNavigator({ ...(Platform.OS === "android" ? { headerShown: false } : SHEET_SOLID_HEADER_OPTIONS), title: "Set up T3 Connect", gestureEnabled: true, - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.6, 0.95], + ...HORIZONTAL_FORM_SHEET_OPTIONS, + sheetAllowedDetents: formSheetDetents(0.6, 0.95), sheetGrabberVisible: true, }, }), @@ -977,7 +979,7 @@ const RootStackConfig = createWorkspaceStackNavigator({ ...(Platform.OS === "android" ? { presentation: "card" as const, headerShown: false } : { - ...FORM_SHEET_PRESENTATION_OPTIONS, + ...HORIZONTAL_FORM_SHEET_OPTIONS, sheetAllowedDetents: [0.55, 0.7], sheetGrabberVisible: true, }), @@ -991,28 +993,11 @@ const RootStackConfig = createWorkspaceStackNavigator({ // native header stack mounts without remounting the pairing form. ...(Platform.OS === "ios" ? SHEET_GLASS_HEADER_OPTIONS : undefined), title: "Add Environment", - ...FORM_SHEET_PRESENTATION_OPTIONS, + ...HORIZONTAL_FORM_SHEET_OPTIONS, sheetAllowedDetents: [0.55, 0.7], sheetGrabberVisible: true, }, }), - NewTaskSheet: createNativeStackScreen({ - screen: NewTaskSheetStack, - linking: "new", - // A screen's layout replaces the navigator's screenLayout. - layout: ({ children, route }) => ( - - - {children} - - - ), - options: { - ...NESTED_NAVIGATOR_OPTIONS, - gestureEnabled: true, - headerShown: false, - }, - }), NotFound: createNativeStackScreen({ screen: NotFoundScreen, linking: "*", @@ -1052,22 +1037,38 @@ export const RootStack = RootStackConfig.with(function AdaptiveRootStack({ Navig const nativeMetrics = useNativeLayoutMetrics(); const usesSplitView = deriveLayout({ width, height, nativeMetrics }).usesSplitView; const usesWorkspaceFlowScreens = Platform.OS === "android" || usesSplitView; + const flowPresentations = useRef(new Map()); return ( } + screenListeners={{ + state: ({ data }) => { + const keys = new Set(data.state.routes.map((route) => route.key)); + for (const key of flowPresentations.current.keys()) { + if (!keys.has(key)) flowPresentations.current.delete(key); + } + }, + }} screenOptions={({ route }) => { - if (route.name !== "SettingsSheet" && route.name !== "NewTaskSheet") { + if (route.name !== "SettingsSheet") { return Platform.OS === "android" ? { animation: androidWorkspaceDestinationAnimation(route.name, usesSplitView) } : {}; } - return usesWorkspaceFlowScreens + // Moving an open flow between a sheet and a split column remounts its nested + // navigator. Keep its presentation until dismissal so folding preserves the settings flow. + let usesCard = flowPresentations.current.get(route.key); + if (usesCard === undefined) { + usesCard = usesWorkspaceFlowScreens; + flowPresentations.current.set(route.key, usesCard); + } + return usesCard ? { presentation: "card" } : { - ...FORM_SHEET_PRESENTATION_OPTIONS, - sheetAllowedDetents: [0.92], + presentation: "formSheet", + sheetAllowedDetents: [1], sheetGrabberVisible: true, }; }} diff --git a/apps/mobile/src/components/ComposerAttachmentStrip.tsx b/apps/mobile/src/components/ComposerAttachmentStrip.tsx index 9cd2d85cef570..ebbfbe9881477 100644 --- a/apps/mobile/src/components/ComposerAttachmentStrip.tsx +++ b/apps/mobile/src/components/ComposerAttachmentStrip.tsx @@ -347,6 +347,8 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) { onPressDocument={props.onPressDocument} /> void; }) { const descriptors = resolveProviderOptionDescriptors({ @@ -48,6 +48,7 @@ export function ComposerModelControl(props: { ] .filter(Boolean) .join(", ")} + size={props.size} disabled={props.disabled} emphasized renderIcon={(size) => @@ -72,8 +73,15 @@ export function ComposerModelControl(props: { label={label} iconOnly={props.iconOnly} suffix={ - props.showSummary !== false && summary ? ( - + summary ? ( + {summary} ) : undefined diff --git a/apps/mobile/src/components/ComposerToolbar.tsx b/apps/mobile/src/components/ComposerToolbar.tsx index f8e3c1447f04f..74708eff8d8c0 100644 --- a/apps/mobile/src/components/ComposerToolbar.tsx +++ b/apps/mobile/src/components/ComposerToolbar.tsx @@ -1,7 +1,6 @@ import type { ComponentProps, ReactNode } from "react"; import { useCallback, useMemo, useState } from "react"; import { - Platform, Pressable, ScrollView, View, @@ -13,7 +12,7 @@ import { type ViewStyle, } from "react-native"; -import Animated, { FadeIn, LinearTransition } from "react-native-reanimated"; +import Animated, { FadeIn } from "react-native-reanimated"; import { cn } from "../lib/cn"; import { AppText as Text } from "./ui/AppText"; @@ -24,11 +23,8 @@ const COMPOSER_TOOLBAR_GAP = 8; const COMPOSER_TOOLBAR_FADE_WIDTH = 18; const COMPOSER_TOOLBAR_SCROLL_EPSILON = 4; const INLINE_CONTROL_LABEL_ENTERING = FadeIn.duration(160); -// Android runs no composer layout transitions, so the toolbar never trails the dock. -const INLINE_CONTROL_WIDTH_TRANSITION = - Platform.OS === "android" ? undefined : LinearTransition.duration(180); - export function ComposerInlineControl(props: { + readonly size?: "regular" | "large"; readonly accessibilityHint?: string; readonly accessibilityLabel?: string; readonly disabled?: boolean; @@ -47,13 +43,11 @@ export function ComposerInlineControl(props: { readonly showChevron?: boolean; }) { const { scale, smallIconSize } = useAndroidControlSizing(); - // The label fades in as the control widens, and leaves at once: an exiting view keeps - // drawing at its old place while the chevron moves into it. The clip keeps content laid out - // at the final width inside the frame while it eases there. + // Keep the picker anchored to its parent while the editor changes height. const animatesLabel = props.iconOnly !== undefined; + const iconSize = props.size === "large" ? 20 : smallIconSize; return ( - {props.renderIcon(smallIconSize)} + {props.renderIcon(iconSize)} ) : props.icon ? ( ["name"]; @@ -263,13 +256,14 @@ export function ComposerActionButton(props: { readonly onTouchStart?: PressableProps["onTouchStart"]; }) { const { scale, smallIconSize } = useAndroidControlSizing(); - const circleSize = Math.round(30 * scale); + const circleSize = Math.round((props.size === "large" ? 44 : 30) * scale); return ( void; }) { const insets = useSafeAreaInsets(); + // NativeColumnContent already insets the sides, so only a book fold moves the reveal handle. + const handleBounds = deriveBottomControlInsets(useNativeColumnLayoutMetrics()); const { themeVariables } = useAppearancePreferences(); const focused = useIsFocused(); const [foreground, setForeground] = useState(AppState.currentState !== "background"); @@ -318,7 +322,10 @@ function DevicePreviewScreen({ accessibilityHint="Shaking the phone also shows them" hitSlop={12} className="absolute h-6 w-16 items-center justify-center self-center" - style={{ top: Math.max(insets.top - 18, 4) }} + style={{ + top: Math.max(insets.top - 18, 4), + transform: [{ translateX: (handleBounds.left - handleBounds.right) / 2 }], + }} onPress={() => setControlsVisible(true)} > diff --git a/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx b/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx index 82de8974654ef..9ef668a9fa1c5 100644 --- a/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx +++ b/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx @@ -337,7 +337,9 @@ function FileContent(props: { type ThreadFilesRouteScreenProps = StaticScreenProps<{ readonly environmentId: string; - readonly threadId: string; + readonly threadId?: string; + readonly cwd?: string; + readonly projectName?: string; }>; type ThreadFileRouteScreenProps = StaticScreenProps<{ @@ -430,6 +432,7 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace( props.route.params, ); + const hasFileInspector = fileInspector.supported && threadId !== null; const revealedInspectorRef = useRef(false); const fileAccessSession = useEnvironmentQuery( environmentId !== null ? environmentSession.sessionStateAtom(environmentId) : null, @@ -444,7 +447,7 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { const { canReadFiles } = fileAccess; const entriesQuery = useFileTreeEntries({ environmentId, - cwd: !canReadFiles || fileInspector.supported ? null : cwd, + cwd: !canReadFiles || hasFileInspector ? null : cwd, searchQuery, }); const handleReturnToThread = () => { @@ -463,16 +466,20 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { }; const handleSelectFile = (path: string) => { - if (environmentId === null || threadId === null) { + if (environmentId === null || cwd === null) { return; } const params = { environmentId: String(environmentId), - threadId: String(threadId), + ...(threadId === null ? { cwd, projectName } : { threadId: String(threadId) }), path: path.split("/").filter((segment) => segment.length > 0), }; + if (threadId === null) { + navigation.navigate("NewTaskFile", params); + return; + } const navigationAction = resolveFileSelectionNavigationAction({ - hasPersistentFileInspector: fileInspector.supported, + hasPersistentFileInspector: hasFileInspector, }); if (navigationAction === "replace") { navigation.dispatch(StackActions.replace("ThreadFile", params)); @@ -503,14 +510,14 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { }); }; useEffect(() => { - if (fileInspector.supported && cwd !== null && !revealedInspectorRef.current) { + if (hasFileInspector && cwd !== null && !revealedInspectorRef.current) { revealedInspectorRef.current = true; showAuxiliaryPane("inspector"); } - }, [cwd, fileInspector.supported, showAuxiliaryPane]); + }, [cwd, hasFileInspector, showAuxiliaryPane]); - if (selectedThread === null || environmentId === null || threadId === null) { - if (fileInspector.supported) { + if (environmentId === null || (threadId !== null && selectedThread === null)) { + if (hasFileInspector) { return ( ; } - if (fileInspector.supported) { + if (hasFileInspector) { return ( - {fileTree} + {fileTree} >(); - const { layout, panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout(); - const usesNativeMailSearchToolbar = useNativeMailSearchToolbar(); - const deviceType = useDeviceType(); - const primaryColumn = use(NativePrimaryColumnContext); - const sidebarHeader = - Platform.OS === "ios" && - primaryColumn !== null && - (deviceType === "pad" || !usesNativeMailSearchToolbar); - const showsCompactCompose = deviceType === "pad" && !layout.usesSplitView; - const searchBarRef = useRef(null); - const focusAfterReveal = useRef(false); - useEffect( - () => - navigation.addListener("transitionEnd", (event) => { - if (focusAfterReveal.current && !event.data.closing) { - focusAfterReveal.current = false; - searchBarRef.current?.focus(); - } - }), - [navigation], - ); - const iconColor = useUniwindTheme()["--color-icon"]; - const hasCustomListOptions = - props.selectedEnvironmentId !== null || props.selectedProjectKey !== null; - const focusSearch = useCallback(() => { - if (primaryColumn && !panes.primarySidebarVisible) { - focusAfterReveal.current = true; - togglePrimarySidebar(); - return true; - } - searchBarRef.current?.focus(); - return searchBarRef.current !== null; - }, [primaryColumn, panes.primarySidebarVisible, togglePrimarySidebar]); - useHardwareKeyboardCommand("focusSearch", focusSearch); - const filterMenu = buildHomeListFilterMenu(props); - - if (props.usesWorkspaceToolbar) { - return deviceType === "duo-unfolded" ? ( - - ) : null; - } - - return ( - <> - - sidebarHeader - ? [ - ...createSidebarHeaderItems({ - filterIcon: hasCustomListOptions - ? "line.3.horizontal.decrease.circle.fill" - : "line.3.horizontal.decrease.circle", - filterMenu, - onOpenSettings: props.onOpenSettings, - }), - ...(showsCompactCompose - ? [ - withNativeGlassHeaderItem({ - accessibilityLabel: "New task", - axisBehavior: "horizontalOnly", - icon: { name: "square.and.pencil", type: "sfSymbol" } as const, - identifier: "home-new-task", - label: "", - onPress: props.onStartNewTask, - type: "button", - }), - ] - : []), - ] - : [ - withNativeGlassHeaderItem({ - accessibilityLabel: "Open settings", - icon: { name: "ellipsis", type: "sfSymbol" } as const, - identifier: "home-settings", - label: "", - onPress: props.onOpenSettings, - type: "button", - }), - ], - // The keys below are set per-branch (not `undefined`) so a later - // reapply cannot clobber options owned by Toolbar. - ...(sidebarHeader - ? { - headerSearchBarOptions: { - ref: searchBarRef, - autoCapitalize: "none" as const, - hideNavigationBar: false, - hideWhenScrolling: false, - obscureBackground: false, - placement: "stacked" as const, - allowToolbarIntegration: false, - placeholder: "Search", - onCancelButtonPress: () => props.onSearchQueryChange(""), - onChangeText: (event) => props.onSearchQueryChange(event.nativeEvent.text), - }, - unstable_headerToolbarItems: () => [], - } - : usesNativeMailSearchToolbar - ? { - headerSearchBarOptions: { - ref: searchBarRef, - autoCapitalize: "none" as const, - onCancelButtonPress: () => props.onSearchQueryChange(""), - }, - unstable_headerToolbarItems: () => [ - createNativeMailSearchToolbarItem({ - composeButtonId: "home-new-task", - composeSystemImageName: "square.and.pencil", - filterMenu, - filterButtonId: "home-filter", - filterSystemImageName: hasCustomListOptions - ? "line.3.horizontal.decrease.circle.fill" - : "line.3.horizontal.decrease", - onComposePress: props.onStartNewTask, - onSearchTextChange: props.onSearchQueryChange, - placeholder: "Search", - searchTextChangeId: "home-search-text", - showsSearchDismissButton: true, - }), - ], - } - : { - headerSearchBarOptions: { - ref: searchBarRef, - autoCapitalize: "none" as const, - hideNavigationBar: false, - placeholder: "Search", - onCancelButtonPress: () => { - props.onSearchQueryChange(""); - }, - onChangeText: (event) => { - props.onSearchQueryChange(event.nativeEvent.text); - }, - }, - }), - }} - /> - - {sidebarHeader || usesNativeMailSearchToolbar ? null : ( - - - - Environment - props.onEnvironmentChange(null)} - subtitle="Show threads from every environment" - > - All environments - - {props.environments.map((environment) => ( - props.onEnvironmentChange(environment.environmentId)} - > - {environment.label} - - ))} - - - {props.projects.length > 0 ? ( - - Project - props.onProjectChange(null)} - subtitle="Show threads from every project" - > - All projects - - {props.projects.map((project) => ( - props.onProjectChange(project.key)} - > - {project.label} - - ))} - - ) : null} - - - - - )} - - ); +/** The iOS thread list lives in the workspace, whose WorkspaceToolbar owns its header and search. */ +export function HomeHeader(_props: HomeHeaderProps) { + return null; } diff --git a/apps/mobile/src/features/home/HomeHeader.types.ts b/apps/mobile/src/features/home/HomeHeader.types.ts index 32648ef843a3e..3c5f580f9eefb 100644 --- a/apps/mobile/src/features/home/HomeHeader.types.ts +++ b/apps/mobile/src/features/home/HomeHeader.types.ts @@ -7,7 +7,6 @@ import type { export type HomeHeaderEnvironment = HomeListFilterMenuEnvironment; export interface HomeHeaderProps { - readonly usesWorkspaceToolbar?: boolean; readonly environments: ReadonlyArray; readonly projects: ReadonlyArray; readonly searchQuery: string; diff --git a/apps/mobile/src/features/home/HomeRouteScreen.tsx b/apps/mobile/src/features/home/HomeRouteScreen.tsx index 8ab642eb44fd3..494966d886f67 100644 --- a/apps/mobile/src/features/home/HomeRouteScreen.tsx +++ b/apps/mobile/src/features/home/HomeRouteScreen.tsx @@ -1,3 +1,4 @@ +import { uuidv4 } from "../../lib/uuid"; import { ThreadUndoNotice } from "./ThreadUndoNotice"; import { Toolbar } from "../../components/ui/Toolbar"; import { use } from "react"; @@ -27,7 +28,7 @@ import { HomeScreen } from "./HomeScreen"; import { HomeHeader } from "./HomeHeader"; import { useHomeListOptions } from "./home-list-options"; import { useAtomValueWhileVisible, useHomeRouteVisible } from "./home-route-visibility"; -import { useHomeThreadSelection } from "./home-thread-navigation"; +import { useHomeThreadRouteSelection, useHomeThreadSelection } from "./home-thread-navigation"; import { buildHomeProjectScopes } from "./homeThreadList"; import { usePendingTaskListActions } from "./usePendingTaskListActions"; import { useThreadListActions } from "./useThreadListActions"; @@ -40,7 +41,7 @@ export function HomeRouteScreen() { const columnMetrics = useNativeColumnLayoutMetrics(); const deviceType = useDeviceType(); const headerWidth = nativePrimaryColumn ? (columnMetrics?.width ?? windowWidth) : windowWidth; - const { layout, panes, primarySidebarSearchQuery, setPrimarySidebarSearchQuery } = + const { layout, panes, primarySidebarSearchQuery, setPrimarySidebarSearchQuery, startNewTask } = useAdaptiveWorkspaceLayout(); const projects = useProjects(); // Streaming turns rewrite thread shells many times a second. While a Thread @@ -65,11 +66,12 @@ export function HomeRouteScreen() { else setLocalSearchQuery(query); }; const handleSelectThread = useHomeThreadSelection(); + const selectThreadRoute = useHomeThreadRouteSelection(); const handleNewThreadOnBranch = useCallback( (thread: EnvironmentThreadShell) => { - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + selectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: String(thread.environmentId), projectId: String(thread.projectId), branch: thread.branch, @@ -77,7 +79,7 @@ export function HomeRouteScreen() { }, }); }, - [navigation], + [selectThreadRoute], ); useEffect(() => { @@ -177,7 +179,7 @@ export function HomeRouteScreen() { navigation.navigate("NewTaskSheet", { screen: "NewTask" })} + onPress={startNewTask} /> ) : null} @@ -193,9 +195,7 @@ export function HomeRouteScreen() { : undefined } onStartNewTask={ - Platform.OS === "android" && panes.primarySidebarVisible - ? undefined - : () => navigation.navigate("NewTaskSheet", { screen: "NewTask" }) + Platform.OS === "android" && panes.primarySidebarVisible ? undefined : startNewTask } /> @@ -205,7 +205,7 @@ export function HomeRouteScreen() { return ( navigation.navigate("NewTaskSheet", { screen: "NewTask" })} + onStartNewTask={startNewTask} > <> {/* Restore the header after leaving split view; screen options are @@ -228,7 +228,6 @@ export function HomeRouteScreen() { /> )} navigation.navigate("NewTaskSheet", { screen: "NewTask" })} + onStartNewTask={startNewTask} /> { - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + selectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: String(project.environmentId), projectId: String(project.id), title: project.title, }, }); }} - onStartNewTask={() => navigation.navigate("NewTaskSheet", { screen: "NewTask" })} + onStartNewTask={startNewTask} pendingTasks={pendingTasks} projectGroupingMode={listOptions.projectGroupingMode} projects={projects} diff --git a/apps/mobile/src/features/home/HomeScreen.tsx b/apps/mobile/src/features/home/HomeScreen.tsx index afa23370fe33a..389054aa1035e 100644 --- a/apps/mobile/src/features/home/HomeScreen.tsx +++ b/apps/mobile/src/features/home/HomeScreen.tsx @@ -1,8 +1,11 @@ +import { useActiveNewTask } from "../../state/active-new-task"; import { AppActivityIndicator } from "../../components/ui/AppActivityIndicator"; import { useAndroidControlSizing } from "../../components/ui/useAndroidControlSizing"; import type { ThreadMoveDestination } from "../threads/threadOrder"; import { computeThreadMoveAvailability } from "../threads/threadOrder"; -import { LegendList, type LegendListRef } from "@legendapp/list/react-native"; +import type { LegendListRef } from "@legendapp/list/react-native"; +import { AnimatedLegendList } from "@legendapp/list/reanimated"; +import { THREAD_SIDEBAR_ROW_LAYOUT } from "../threads/threadListAnimations"; import { type EnvironmentProject, type EnvironmentThreadShell, @@ -42,6 +45,7 @@ import type { SavedRemoteConnection } from "../../lib/connection"; import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities"; import { NativePrimaryColumnContext } from "../../native/v5-workspace-context"; import { nativeHeaderScrollEdgeEffects } from "../../native/scrollEdgeEffects"; +import { useRestingTopOffset } from "../../native/useRestingTopOffset"; import { useNativeColumnLayoutMetrics, useNativeLayoutMetrics, @@ -216,6 +220,7 @@ function HomeTopContentSpacer() { function HomeScrollView(props: ComponentProps) { const insets = useSafeAreaInsets(); const primaryColumn = use(NativePrimaryColumnContext); + const restingOffset = useRestingTopOffset(insets.top, props); if (Platform.OS !== "ios") return ; // v5 needs the actual content scroll view registered with its screen controller. @@ -230,6 +235,7 @@ function HomeScrollView(props: ComponentProps) { > ) { // past its edge, activating scroll-edge protection before any scroll. automaticallyAdjustContentInsets={false} contentInsetAdjustmentBehavior="never" - contentOffset={props.contentOffset ?? { x: 0, y: -insets.top }} /> ); @@ -260,7 +265,8 @@ export function HomeScreen(props: HomeScreenProps) { Platform.OS === "android" ? "bg-header" : contentBackground, ); const columnMetrics = useNativeColumnLayoutMetrics(); - const selectedThreadKey = primaryColumn?.selectedThreadKey ?? null; + const activeNewTask = useActiveNewTask(); + const selectedThreadKey = activeNewTask ? null : (primaryColumn?.selectedThreadKey ?? null); const fullSwipeWidth = primaryColumn && columnMetrics ? columnMetrics.width - 20 : undefined; const queuedThreadKeys = useQueuedThreadKeys(); const openSwipeableRef = useRef(null); @@ -577,7 +583,14 @@ export function HomeScreen(props: HomeScreenProps) { ); return ( ); } @@ -700,6 +718,7 @@ export function HomeScreen(props: HomeScreenProps) { // every HomeScreen render — and the minute clock must stay out of it for // the same reason: the clock text is precomputed per item instead. const v2ExtraData = { + activeNewTask, projectByKey, projectTitleByProjectKey: v2ProjectTitleByProjectKey, listEnvironments, @@ -837,7 +856,8 @@ export function HomeScreen(props: HomeScreenProps) { > - activateVisibleRows(threadListV2Items)} onTouchStart={(event) => trackListTouches(event, true)} diff --git a/apps/mobile/src/features/home/home-thread-navigation.test.ts b/apps/mobile/src/features/home/home-thread-navigation.test.ts index 3f2b4a4fee4ff..820a666650548 100644 --- a/apps/mobile/src/features/home/home-thread-navigation.test.ts +++ b/apps/mobile/src/features/home/home-thread-navigation.test.ts @@ -26,7 +26,10 @@ vi.mock("@react-navigation/native", () => { return { CommonActions, StackActions }; }); -import { createHomeThreadNavigationAction } from "./home-thread-navigation"; +import { + createHomeThreadRouteAction, + createHomeThreadNavigationAction, +} from "./home-thread-navigation"; const { StackActions, StackRouter } = loadRouters(); const routeNames = ["Home", "Thread"]; @@ -196,3 +199,36 @@ describe("createHomeThreadNavigationAction", () => { }); }); }); + +describe("draft and live thread selection", () => { + it("keeps the screen key and clears old identity when switching both ways", () => { + const live = selectThread(initialState(), thread("thread-a")); + const key = live.routes[live.index]!.key; + const draft = apply( + live, + createHomeThreadRouteAction({ + state: live, + dismissingRouteKey: null, + params: { draft: { draftId: "new-task:draft-a" } }, + }), + ); + expect(draft.routes[draft.index]).toMatchObject({ + key, + params: { + environmentId: undefined, + threadId: undefined, + draft: { draftId: "new-task:draft-a" }, + }, + }); + const restored = selectThread(draft, thread("thread-b")); + expect(restored.routes[restored.index]).toMatchObject({ + key, + params: { + environmentId: "environment-1", + threadId: "thread-b", + draft: undefined, + }, + }); + expect(restored.routes).toHaveLength(live.routes.length); + }); +}); diff --git a/apps/mobile/src/features/home/home-thread-navigation.ts b/apps/mobile/src/features/home/home-thread-navigation.ts index 317b680591571..94948bb8f5ac8 100644 --- a/apps/mobile/src/features/home/home-thread-navigation.ts +++ b/apps/mobile/src/features/home/home-thread-navigation.ts @@ -12,27 +12,44 @@ import { useAndroidWorkspaceSelection } from "../workspace/useAndroidWorkspaceSe type ThreadSelection = Pick; -export function createHomeThreadNavigationAction(input: { +export function createHomeThreadRouteAction(input: { readonly state: Pick; readonly dismissingRouteKey: string | null; - readonly thread: ThreadSelection; + readonly params: ReactNavigation.RootParamList["Thread"]; }) { const currentRoute = input.state.routes[input.state.index]; - const params = { - environmentId: input.thread.environmentId, - threadId: input.thread.id, - }; - - // Native swipe-back pops the outgoing route after its animation. Reusing - // that key would also discard this selection when the dismissal arrives. if (input.dismissingRouteKey !== null && currentRoute?.key === input.dismissingRouteKey) { - return StackActions.push("Thread", params); + return StackActions.push("Thread", input.params); + } + if (currentRoute?.name === "Thread") { + return { + ...CommonActions.setParams({ + environmentId: undefined, + threadId: undefined, + draft: undefined, + ...input.params, + }), + source: currentRoute.key, + }; } + return CommonActions.navigate("Thread", input.params); +} - return CommonActions.navigate("Thread", params); +export function createHomeThreadNavigationAction(input: { + readonly state: Pick; + readonly dismissingRouteKey: string | null; + readonly thread: ThreadSelection; +}) { + return createHomeThreadRouteAction({ + ...input, + params: { + environmentId: input.thread.environmentId, + threadId: input.thread.id, + }, + }); } -export function useHomeThreadSelection() { +export function useHomeThreadRouteSelection() { const navigation = useNavigation>(); const dismissingRouteKey = useRef(null); @@ -60,22 +77,31 @@ export function useHomeThreadSelection() { }, [navigation]); return useCallback( - (thread: ThreadSelection) => { + (params: ReactNavigation.RootParamList["Thread"]) => { if (Platform.OS === "android") { selectAndroidWorkspace({ name: "Thread", - params: { environmentId: String(thread.environmentId), threadId: String(thread.id) }, + params, }); return; } navigation.dispatch((state) => - createHomeThreadNavigationAction({ + createHomeThreadRouteAction({ state, dismissingRouteKey: dismissingRouteKey.current, - thread, + params, }), ); }, [navigation, selectAndroidWorkspace], ); } + +export function useHomeThreadSelection() { + const selectRoute = useHomeThreadRouteSelection(); + return useCallback( + (thread: ThreadSelection) => + selectRoute({ environmentId: thread.environmentId, threadId: thread.id }), + [selectRoute], + ); +} diff --git a/apps/mobile/src/features/home/thread-swipe-actions.tsx b/apps/mobile/src/features/home/thread-swipe-actions.tsx index a320e7b21e37b..33c4a44d05244 100644 --- a/apps/mobile/src/features/home/thread-swipe-actions.tsx +++ b/apps/mobile/src/features/home/thread-swipe-actions.tsx @@ -62,6 +62,7 @@ interface ThreadSwipeAction { readonly accessibilityLabel: string; readonly icon: ComponentProps["name"]; readonly label: string; + readonly destructive?: boolean; readonly menu?: { readonly actions: MenuAction[]; readonly onPressAction: NonNullable["onPressAction"]>; @@ -726,7 +727,7 @@ export function ThreadSwipeActions(props: { void, +): { readonly openPendingTask: (pendingTask: PendingNewTask) => void; readonly confirmDeletePendingTask: (pendingTask: PendingNewTask) => void; } { - const navigation = useNavigation(); + const selectHomeRoute = useHomeThreadRouteSelection(); + const selectThreadRoute = selectRoute ?? selectHomeRoute; const openPendingTask = useCallback( (pendingTask: PendingNewTask) => { - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { - environmentId: String(pendingTask.environmentId), - projectId: String(pendingTask.projectId), - ...(pendingTask.kind === "pending" - ? { pendingTaskId: String(pendingTask.message.messageId) } - : { draftId: pendingTask.draftKey }), - }, - }); + selectThreadRoute( + pendingTask.kind === "pending" + ? { + environmentId: String(pendingTask.environmentId), + threadId: String(pendingTask.message.threadId), + } + : { + draft: { + environmentId: String(pendingTask.environmentId), + projectId: String(pendingTask.projectId), + draftId: pendingTask.draftKey, + }, + }, + ); }, - [navigation], + [selectThreadRoute], ); const confirmDeletePendingTask = useCallback((pendingTask: PendingNewTask) => { @@ -37,10 +50,15 @@ export function usePendingTaskListActions(): { text: "Discard", style: "destructive", onPress: () => { - clearComposerDraftContent(pendingTask.draftKey, { - clearModelSelection: true, - clearWorkspaceSelection: true, - }); + void withThreadDismissal( + pendingTask.key, + async () => + clearComposerDraftContent(pendingTask.draftKey, { + clearModelSelection: true, + clearWorkspaceSelection: true, + }), + () => true, + ); }, }, ]); @@ -55,12 +73,28 @@ export function usePendingTaskListActions(): { text: "Delete", style: "destructive", onPress: () => { - // Release the edit lock only after removal succeeds, and only if - // it is held for THIS task — clearing it up front (or for another - // task) would let the drain deliver a mid-edit payload. - void removeThreadOutboxMessage(pendingTask.message) - .then(() => releaseEditingQueuedMessage(pendingTask.message.messageId)) + // The row animates out before removal runs, so hold delivery from + // confirmation on: a reconnect must not send a task being deleted. + // The hold is shared with the editor, which could release it + // mid-animation, so a task open in the editor is removed at once + // and keeps its hold unless removal succeeds. + const messageId = pendingTask.message.messageId; + const heldByEditor = appAtomRegistry.get(editingQueuedMessageIdsAtom)[messageId]; + holdEditingQueuedMessage(messageId); + const releaseUnlessEditing = () => { + if (!heldByEditor) releaseEditingQueuedMessage(messageId); + }; + const remove = () => removeThreadOutboxMessage(pendingTask.message); + void ( + heldByEditor + ? remove() + : withThreadDismissal(pendingTask.key, remove, (removed) => removed) + ) + .then((removed) => + removed ? releaseEditingQueuedMessage(messageId) : releaseUnlessEditing(), + ) .catch((error) => { + releaseUnlessEditing(); Dialog.alert( "Could not delete pending task", error instanceof Error ? error.message : "The pending task could not be removed.", diff --git a/apps/mobile/src/features/home/useThreadListActions.ts b/apps/mobile/src/features/home/useThreadListActions.ts index 837887a73d6ed..0f18f24923cc4 100644 --- a/apps/mobile/src/features/home/useThreadListActions.ts +++ b/apps/mobile/src/features/home/useThreadListActions.ts @@ -456,10 +456,15 @@ export function useThreadListActions(): { // Pinning clears snooze, so older Settled or Snoozed undos would fight this pin. ThreadUndo.invalidate("settle", scopedThreadKey(thread.environmentId, thread.id)); ThreadUndo.invalidate("snooze", scopedThreadKey(thread.environmentId, thread.id)); - const result = await pinMutation({ - environmentId: thread.environmentId, - input: { threadId: thread.id, ...(orderKey !== undefined ? { orderKey } : {}) }, - }); + const result = await withThreadDismissal( + scopedThreadKey(thread.environmentId, thread.id), + () => + pinMutation({ + environmentId: thread.environmentId, + input: { threadId: thread.id, ...(orderKey !== undefined ? { orderKey } : {}) }, + }), + (result) => result._tag === "Success", + ); if (result._tag === "Failure") { const error = Cause.squash(result.cause); Dialog.alert( @@ -484,10 +489,15 @@ export function useThreadListActions(): { selectionHaptic(); const plan = ThreadUndo.planThreadUndo("unpin", thread); const claim = ThreadUndo.begin("pin", scopedThreadKey(thread.environmentId, thread.id)); - const result = await unpinMutation({ - environmentId: thread.environmentId, - input: { threadId: thread.id }, - }); + const result = await withThreadDismissal( + scopedThreadKey(thread.environmentId, thread.id), + () => + unpinMutation({ + environmentId: thread.environmentId, + input: { threadId: thread.id }, + }), + (result) => result._tag === "Success", + ); if (result._tag === "Failure") { claim.finish(); const error = Cause.squash(result.cause); diff --git a/apps/mobile/src/features/keyboard/CommandPalette.tsx b/apps/mobile/src/features/keyboard/CommandPalette.tsx index d264c0765bcf5..c70062ab04c7c 100644 --- a/apps/mobile/src/features/keyboard/CommandPalette.tsx +++ b/apps/mobile/src/features/keyboard/CommandPalette.tsx @@ -1,3 +1,4 @@ +import { uuidv4 } from "../../lib/uuid"; import { useNavigation } from "@react-navigation/native"; import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search"; import { THREAD_JUMP_KEYBINDING_COMMANDS } from "@t3tools/contracts"; @@ -22,7 +23,9 @@ import { RowPressable } from "../../components/ui/RowPressable"; import { AppText as Text } from "../../components/ui/AppText"; import { SymbolView, type AppSymbolName } from "../../components/ui/AppSymbol"; import { cn } from "../../lib/cn"; +import { deriveBottomControlInsets } from "../../lib/reserved-regions"; import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities"; +import { useNativeLayoutMetrics } from "../../native/native-layout-metrics"; import { T3KeyboardCommands } from "../../native/T3KeyboardCommands"; import { useProjects, useThreadShell, useThreadShells } from "../../state/entities"; import { useThreadSearch } from "../../state/queries"; @@ -140,7 +143,7 @@ export function CommandPalette(props: { }) { const navigation = useNavigation(); const { themeVariables } = useAppearancePreferences(); - const { selectThread } = useAdaptiveWorkspaceLayout(); + const { selectThread, selectThreadRoute } = useAdaptiveWorkspaceLayout(); const runCommand = props.onCommand; const projects = useProjects(); const threads = useThreadShells(); @@ -156,6 +159,9 @@ export function CommandPalette(props: { const inputRef = useRef(null); const listRef = useRef>(null); const { width, height } = useWindowDimensions(); + // The palette floats above every column, so it keeps itself inside the window's side insets + // and to one side of a book fold. + const bounds = deriveBottomControlInsets(useNativeLayoutMetrics()); const searchEnvironmentIds = useMemo( () => environments @@ -185,14 +191,14 @@ export function CommandPalette(props: { kind: "action", title: "New thread in…", searchTerms: ["new task", "chat", "create", "project"], - run: () => navigation.navigate("NewTaskSheet", { screen: "NewTask" }), + run: () => runCommand("newTask"), }, { key: "addProject", kind: "action", title: "Add project", searchTerms: ["folder", "clone", "repository", "git"], - run: () => navigation.navigate("NewTaskSheet", { screen: "AddProject" }), + run: () => navigation.navigate("AddProject"), }, { key: "settings", @@ -274,9 +280,9 @@ export function CommandPalette(props: { title: `New thread in ${activeProject.title}`, searchTerms: ["new task", "chat", "create"], run: () => - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + selectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: activeProject.environmentId, projectId: activeProject.id, title: activeProject.title, @@ -308,9 +314,9 @@ export function CommandPalette(props: { detail: `New thread · ${savedConnectionsById[project.environmentId]?.environmentLabel ?? project.environmentId}`, searchTerms: [project.workspaceRoot, "new thread", "project"], run: () => - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + selectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: project.environmentId, projectId: project.id, title: project.title, @@ -351,6 +357,7 @@ export function CommandPalette(props: { runCommand, savedConnectionsById, selectThread, + selectThreadRoute, threads, ]); const results = useMemo( @@ -423,7 +430,8 @@ export function CommandPalette(props: { void; + readonly deferThreadSelectionUntil: (operation: Promise) => void; readonly selectThread: (thread: EnvironmentThreadShell) => void; + readonly selectThreadRoute: (params: ReactNavigation.RootParamList["Thread"]) => void; readonly activateAuxiliaryPaneRole: (role: WorkspaceAuxiliaryPaneRole) => () => void; readonly registerWorkspaceInspector: (render: () => ReactNode) => () => void; readonly setPrimarySidebarSearchQuery: (query: string) => void; @@ -126,7 +135,10 @@ const AdaptiveWorkspaceContext = createContext({ panes: compactPanes, fileInspector: compactFileInspector, primarySidebarSearchQuery: "", + startNewTask: () => undefined, + deferThreadSelectionUntil: () => undefined, selectThread: () => undefined, + selectThreadRoute: () => undefined, activateAuxiliaryPaneRole: () => () => undefined, registerWorkspaceInspector: () => () => undefined, setPrimarySidebarSearchQuery: () => undefined, @@ -485,10 +497,94 @@ function AdaptiveWorkspaceLayoutContent( }); }; + const selectAndroidWorkspace = useAndroidWorkspaceSelection(); + const deferredThreadSelection = useMemo(() => createDeferredNavigation(), []); + function handleSelectThreadRoute(params: ReactNavigation.RootParamList["Thread"]) { + deferredThreadSelection.run(() => applyThreadRouteSelection(params)); + } + function applyThreadRouteSelection(params: ReactNavigation.RootParamList["Thread"]) { + if (Platform.OS === "android") { + setFileInspectorPreferredVisible(false); + selectAndroidWorkspace({ name: "Thread", params }); + return; + } + const navigationAction = resolveThreadSelectionNavigationAction({ + usesSplitView: layout.usesSplitView, + pathname, + workspaceRouteName: navigation + .getState() + ?.routes.find((route) => route.key === props.workspaceRouteKey)?.name, + }); + const overlayState = resolveThreadSelectionOverlayState({ + state: navigation.getState(), + workspaceRouteKey: props.workspaceRouteKey, + action: navigationAction, + params, + }); + if (overlayState !== null) { + setFileInspectorPreferredVisible(false); + navigation.dispatch(CommonActions.reset(overlayState)); + return; + } + if (navigationAction === "set-params") { + setFileInspectorPreferredVisible(false); + const state = navigation.getState(); + const route = state?.routes.find((candidate) => candidate.key === props.workspaceRouteKey); + if (route?.name === "Thread") { + navigation.dispatch({ + ...CommonActions.setParams({ + environmentId: undefined, + threadId: undefined, + draft: undefined, + ...params, + }), + source: route.key, + }); + } else { + navigation.navigate("Thread", params); + } + return; + } + if (navigationAction === "replace") { + setFileInspectorPreferredVisible(false); + navigation.dispatch(StackActions.replace("Thread", params)); + return; + } + navigation.navigate("Thread", params); + } + + const projects = useProjects(); const handleStartNewTask = () => { - navigation.navigate("NewTaskSheet", { screen: "NewTask" }); + const thread = appAtomRegistry + .get(environmentThreadShells.navigationThreadShellsAtom) + .find((candidate) => candidate.environmentId === environmentId && candidate.id === threadId); + const currentRoute = navigation + .getState() + ?.routes.find((route) => route.key === props.workspaceRouteKey); + const draft = + currentRoute?.name === "Thread" + ? (currentRoute.params as ReactNavigation.RootParamList["Thread"])?.draft + : undefined; + const first = (value: string | string[] | undefined) => + Array.isArray(value) ? value[0] : value; + const draftEnvironmentId = first(draft?.environmentId); + const draftProjectId = first(draft?.projectId); + const currentProject = thread + ? { environmentId: thread.environmentId, id: thread.projectId } + : projects.find( + (project) => + project.environmentId === draftEnvironmentId && project.id === draftProjectId, + ); + handleSelectThreadRoute( + newTaskDestination(layout.usesSplitView, projects, currentProject, uuidv4()), + ); }; + useHardwareKeyboardCommand("newTask", () => { + handleStartNewTask(); + return true; + }); + // Minted here (root stack navigation) so the sidebar pane stays free of // navigation hooks — on iOS it renders inside an independent nav tree. const handleOpenEnvironmentSettings = () => { @@ -499,12 +595,13 @@ function AdaptiveWorkspaceLayoutContent( }; const handleOpenThreadPullRequests = useOpenThreadPullRequests(); - const { openPendingTask: handleOpenPendingTask } = usePendingTaskListActions(); + const { openPendingTask: handleOpenPendingTask } = + usePendingTaskListActions(handleSelectThreadRoute); const handleNewThreadOnBranch = (thread: EnvironmentThreadShell) => { - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + handleSelectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: String(thread.environmentId), projectId: String(thread.projectId), branch: thread.branch, @@ -514,9 +611,9 @@ function AdaptiveWorkspaceLayoutContent( }; const handleNewThreadInProject = (project: EnvironmentProject) => { - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { + handleSelectThreadRoute({ + draft: { + requestId: uuidv4(), environmentId: String(project.environmentId), projectId: String(project.id), title: project.title, @@ -684,47 +781,11 @@ function AdaptiveWorkspaceLayoutContent( ), ); - const selectAndroidWorkspace = useAndroidWorkspaceSelection(); const handleSelectThread = (thread: EnvironmentThreadShell) => { - const params = { + handleSelectThreadRoute({ environmentId: String(thread.environmentId), threadId: String(thread.id), - }; - if (Platform.OS === "android") { - setFileInspectorPreferredVisible(false); - selectAndroidWorkspace({ name: "Thread", params }); - return; - } - const navigationAction = resolveThreadSelectionNavigationAction({ - usesSplitView: layout.usesSplitView, - pathname, - }); - const overlayState = resolveThreadSelectionOverlayState({ - state: navigation.getState(), - workspaceRouteKey: props.workspaceRouteKey, - action: navigationAction, - params, }); - if (overlayState !== null) { - setFileInspectorPreferredVisible(false); - navigation.dispatch(CommonActions.reset(overlayState)); - return; - } - if (navigationAction === "set-params") { - const nextThreadKey = scopedThreadKey(thread.environmentId, thread.id); - if (nextThreadKey === selectedThreadKey) { - return; - } - setFileInspectorPreferredVisible(false); - navigation.navigate("Thread", params); - return; - } - if (navigationAction === "replace") { - setFileInspectorPreferredVisible(false); - navigation.dispatch(StackActions.replace("Thread", params)); - return; - } - navigation.navigate("Thread", params); }; const contextValue = { @@ -732,7 +793,10 @@ function AdaptiveWorkspaceLayoutContent( panes, fileInspector, primarySidebarSearchQuery, + startNewTask: handleStartNewTask, + deferThreadSelectionUntil: deferredThreadSelection.waitFor, selectThread: handleSelectThread, + selectThreadRoute: handleSelectThreadRoute, activateAuxiliaryPaneRole, registerWorkspaceInspector, setPrimarySidebarSearchQuery, @@ -881,6 +945,7 @@ function AdaptiveWorkspaceLayoutContent( style={railOverlay ? { right: 0 } : { width: navigationRailWidth }} > void; readonly onAddConnection?: () => void; }) { + const pullRequests = props.tab === "pullRequests"; return ( - Select a thread + + {pullRequests ? "Select a pull request" : "Select a thread"} + - {Platform.OS === "android" - ? "Choose a thread from the sidebar or start a new thread." - : "Choose a thread from the sidebar or start a new task."} + {pullRequests + ? "Choose a pull request from the sidebar to review it." + : Platform.OS === "android" + ? "Choose a thread from the sidebar or start a new thread." + : "Choose a thread from the sidebar or start a new task."} - {props.onStartNewTask ? ( + {props.onStartNewTask && !pullRequests ? ( Platform.OS === "android" ? ( ) : ( @@ -58,7 +65,7 @@ export function WorkspaceEmptyDetail(props: { className="mt-2 flex-row items-center gap-2 rounded-full bg-primary px-5 py-3 active:opacity-70" onPress={props.onStartNewTask} > - New Task + New thread ) ) : null} diff --git a/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx b/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx index c4c0946fffd02..262e0ac71676f 100644 --- a/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx +++ b/apps/mobile/src/features/layout/createWorkspaceStackNavigator.ios.tsx @@ -14,7 +14,6 @@ import { } from "@react-navigation/native"; import { NativeStackView, - type NativeStackHeaderItemMenu, type NativeStackNavigationEventMap, type NativeStackNavigationOptions, type NativeStackNavigatorProps, @@ -25,9 +24,10 @@ import { View } from "react-native"; import { Split, type SplitHostCommands } from "react-native-screens"; import { EnvironmentId, ThreadId } from "@t3tools/contracts"; import { scopedThreadKey } from "../../lib/scopedEntities"; -import { WorkspaceTabsProvider } from "../workspace/WorkspaceTabsContext"; +import { WorkspaceTabsProvider, useWorkspaceTabs } from "../workspace/WorkspaceTabsContext"; import { WorkspaceNativeTabs } from "../workspace/WorkspaceNativeTabs.ios"; -import { useNativeLayoutMetrics } from "../../native/native-layout-metrics"; +import { isDuoDevice } from "../../lib/device-type"; +import { useDeviceType, useNativeLayoutMetrics } from "../../native/native-layout-metrics"; import { useAdaptiveWorkspaceLayout } from "./AdaptiveWorkspaceLayout"; import { WorkspaceEmptyDetail } from "./WorkspaceEmptyDetail"; @@ -48,6 +48,7 @@ import { nativeWorkspacePopAction, projectWorkspaceStack, partitionStackPresentations, + reconcileStackScreens, type CollapsedSplitColumn, } from "../../native/workspace-stack-projection"; import { @@ -60,14 +61,20 @@ type ViewProps = ComponentProps; type Descriptor = ViewProps["descriptors"][string]; type Route = ViewProps["state"]["routes"][number]; +const isPullRequestRoute = (route: Route) => + route.name === "PullRequest" || + route.name === "PullRequestFile" || + route.name === "PullRequestFiles"; + function ColumnScreen(props: { readonly descriptor: Descriptor; readonly primary?: boolean; + readonly attached?: boolean; readonly selectedThreadKey?: string | null; readonly selectedPullRequestKey?: string | null; - readonly sidebarFilter?: NativeStackHeaderItemMenu; - readonly onDismiss: (routeKey: string) => void; + readonly onDismiss: (routeKey: string, native: boolean) => void; readonly onAppearance: (type: "willAppear" | "didAppear" | "willDisappear", key: string) => void; + readonly onCompose?: () => void; readonly navigation: ViewProps["navigation"]; }) { const { descriptor } = props; @@ -85,8 +92,9 @@ function ColumnScreen(props: { return ( { - if (event.nativeEvent.isNativeDismiss) props.onDismiss(key); + props.onDismiss(key, event.nativeEvent.isNativeDismiss); }} onWillAppear={() => { props.onAppearance("willAppear", key); @@ -109,8 +117,14 @@ function ColumnScreen(props: { (), + }); + if ( + screens.observedRoutes !== props.detail || + screens.observedDescriptors !== props.descriptors + ) { + const routes = reconcileStackScreens(screens.routes, props.detail, screens.completed); + const retained = new Set(routes.map((route) => route.key)); + const active = new Set(props.detail.map((route) => route.key)); + setScreens({ + routes, + descriptors: Object.fromEntries( + Object.entries({ ...screens.descriptors, ...props.descriptors }).filter(([key]) => + retained.has(key), + ), + ), + observedRoutes: props.detail, + observedDescriptors: props.descriptors, + completed: new Set([...screens.completed].filter((key) => active.has(key))), + }); + } + + const selectedTab = useWorkspaceTabs()?.state.selectedTab; + const openNewTask = startNewTask; + // The Duo keeps compose in the detail column's bar, which is its vertical bar in landscape + // and folded, so it sits with the column's other actions whichever screen is shown. Pull + // requests leave it out, including one opened from a thread; their bar is already full of + // review actions. + const detailCompose = + isDuoDevice(deviceType) && selectedTab !== "pullRequests" ? openNewTask : undefined; const inspector = use(NativeWorkspaceInspectorContext); const hostRef = useRef(null); const activeDetailKey = props.detail.at(-1)?.key; @@ -139,12 +191,7 @@ function WorkspaceColumns( const collapsed = useRef(!layout.usesSplitView); const collapsedColumn = useRef({ visible: compactColumn, leaving: null }); const threadParams = props.detail.findLast((route) => route.name === "Thread")?.params; - const pullRequestParams = props.detail.findLast( - (route) => - route.name === "PullRequest" || - route.name === "PullRequestFile" || - route.name === "PullRequestFiles", - )?.params; + const pullRequestParams = props.detail.findLast(isPullRequestRoute)?.params; const selectedPullRequestKey = pullRequestParams && "environmentId" in pullRequestParams && @@ -169,10 +216,17 @@ function WorkspaceColumns( ) : null; const handleNativeDismiss = useCallback( - (key: string) => { - const state = props.navigation.getState(); - const action = nativeWorkspacePopAction(state, key); - if (action) props.navigation.dispatch(action); + (key: string, native: boolean) => { + setScreens((current) => ({ + ...current, + routes: current.routes.filter((route) => route.key !== key), + completed: new Set([...current.completed, key]), + })); + if (native) { + const state = props.navigation.getState(); + const action = nativeWorkspacePopAction(state, key); + if (action) props.navigation.dispatch(action); + } }, [props.navigation], ); @@ -199,7 +253,6 @@ function WorkspaceColumns( const primary = props.descriptors[props.primary.key]; if (!primary) return null; - const sidebarFilter = (primary.options as AppNativeStackNavigationOptions).nativeSidebarFilter; return ( - props.navigation.navigate("NewTaskSheet", { screen: "NewTask" }) + tab={selectedTab} + onStartNewTask={ + // Duo provides compose in the detail bar; iPad uses the empty-state action. + detailCompose ? undefined : openNewTask } /> - {props.detail.map((route) => { - const descriptor = props.descriptors[route.key]; + {screens.routes.map((route) => { + const descriptor = props.descriptors[route.key] ?? screens.descriptors[route.key]; return descriptor ? ( current.key === route.key)} onDismiss={handleNativeDismiss} onAppearance={followCollapsedSplit} + onCompose={isPullRequestRoute(route) ? undefined : detailCompose} navigation={props.navigation} - sidebarFilter={sidebarFilter} /> ) : null; })} @@ -291,7 +351,9 @@ function WorkspaceColumns( @@ -300,7 +362,7 @@ function WorkspaceColumns( } function WorkspaceStackView(props: ViewProps & { readonly nativeColumns: boolean }) { - const { layout } = useAdaptiveWorkspaceLayout(); + const { layout, startNewTask } = useAdaptiveWorkspaceLayout(); // Native dismiss commits the route pop after the transition. Restore the dock // when the primary screen starts appearing, including interactive back gestures. const [primaryVisible, setPrimaryVisible] = useState(false); @@ -327,7 +389,7 @@ function WorkspaceStackView(props: ViewProps & { readonly nativeColumns: boolean options: { headerShown: false }, render: () => ( props.navigation.navigate("NewTaskSheet", { screen: "NewTask" })} + onNewTask={startNewTask} hidden={ projection.detail.length > 0 && !primaryVisible && diff --git a/apps/mobile/src/features/layout/deferred-navigation.test.ts b/apps/mobile/src/features/layout/deferred-navigation.test.ts new file mode 100644 index 0000000000000..4e4c54cdb5945 --- /dev/null +++ b/apps/mobile/src/features/layout/deferred-navigation.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vite-plus/test"; +import { createDeferredNavigation } from "./deferred-navigation"; + +describe("workspace mutation navigation", () => { + it("keeps only the latest selection until all checkouts settle", async () => { + const navigation = createDeferredNavigation(); + const first = Promise.withResolvers(); + const second = Promise.withResolvers(); + const selected: string[] = []; + navigation.waitFor(first.promise); + navigation.waitFor(second.promise); + navigation.run(() => selected.push("old draft")); + navigation.run(() => selected.push("latest draft")); + first.resolve(); + await first.promise; + expect(selected).toEqual([]); + second.resolve(); + await second.promise; + expect(selected).toEqual(["latest draft"]); + navigation.run(() => selected.push("thread")); + expect(selected).toEqual(["latest draft", "thread"]); + }); + + it("waits for the removal guard commit after checkout completes", async () => { + const navigation = createDeferredNavigation(); + const checkout = Promise.withResolvers(); + const guardCommit = Promise.withResolvers(); + let selected = false; + const ready = Promise.allSettled([checkout.promise, guardCommit.promise]); + navigation.waitFor(ready); + navigation.run(() => { + selected = true; + }); + checkout.resolve(); + await checkout.promise; + expect(selected).toBe(false); + guardCommit.resolve(); + await ready; + expect(selected).toBe(true); + }); + + it("releases selection after a failed checkout", async () => { + const navigation = createDeferredNavigation(); + const checkout = Promise.withResolvers(); + let selected = false; + navigation.waitFor(checkout.promise); + navigation.run(() => { + selected = true; + }); + checkout.reject(new Error("Checkout failed")); + await checkout.promise.catch(() => undefined); + expect(selected).toBe(true); + }); +}); diff --git a/apps/mobile/src/features/layout/deferred-navigation.ts b/apps/mobile/src/features/layout/deferred-navigation.ts new file mode 100644 index 0000000000000..8da68b10b394c --- /dev/null +++ b/apps/mobile/src/features/layout/deferred-navigation.ts @@ -0,0 +1,22 @@ +/** Hold in-place route changes while a workspace mutation is still running. */ +export function createDeferredNavigation() { + let pending = 0; + let latest: (() => void) | null = null; + return { + run(action: () => void) { + if (pending > 0) latest = action; + else action(); + }, + waitFor(operation: Promise) { + pending += 1; + const release = () => { + pending -= 1; + if (pending !== 0) return; + const action = latest; + latest = null; + action?.(); + }; + void operation.then(release, release); + }, + }; +} diff --git a/apps/mobile/src/features/layout/useWorkspaceSidebarHeaderItem.ts b/apps/mobile/src/features/layout/useWorkspaceSidebarHeaderItem.ts new file mode 100644 index 0000000000000..f1304ed276633 --- /dev/null +++ b/apps/mobile/src/features/layout/useWorkspaceSidebarHeaderItem.ts @@ -0,0 +1,27 @@ +import { useMemo } from "react"; +import { useNativeWorkspaceColumnsSupported } from "../../native/NativeWorkspaceColumns"; +import { useAdaptiveWorkspaceLayout } from "./AdaptiveWorkspaceLayout"; +import { withNativeGlassHeaderItem } from "./native-glass-header-items"; + +export function useWorkspaceSidebarHeaderItem() { + const usesNativeWorkspaceColumns = useNativeWorkspaceColumnsSupported(); + const { panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout(); + return useMemo( + () => + withNativeGlassHeaderItem({ + axisBehavior: usesNativeWorkspaceColumns ? "horizontalOnly" : undefined, + accessibilityLabel: panes.primarySidebarVisible + ? "Maximize content" + : "Show thread sidebar", + icon: { + name: panes.primarySidebarVisible ? "arrow.up.left.and.arrow.down.right" : "sidebar.left", + type: "sfSymbol" as const, + }, + identifier: "thread-left-sidebar", + label: panes.primarySidebarVisible ? "Maximize content" : "Show thread sidebar", + onPress: togglePrimarySidebar, + type: "button" as const, + }), + [panes.primarySidebarVisible, togglePrimarySidebar, usesNativeWorkspaceColumns], + ); +} diff --git a/apps/mobile/src/features/projects/AddProjectScreen.tsx b/apps/mobile/src/features/projects/AddProjectScreen.tsx index 8dc8750ab0c52..66a68b2ebae27 100644 --- a/apps/mobile/src/features/projects/AddProjectScreen.tsx +++ b/apps/mobile/src/features/projects/AddProjectScreen.tsx @@ -54,7 +54,13 @@ import { resolveEnvironmentMachineKind, SourceControlProviderKind, } from "@t3tools/contracts"; -import { CommonActions, StackActions, useNavigation } from "@react-navigation/native"; +import { + CommonActions, + StackActions, + useNavigation, + type NavigationState, + type ParamListBase, +} from "@react-navigation/native"; import { SymbolView } from "../../components/ui/AppSymbol"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { Platform, Pressable, View } from "react-native"; @@ -648,7 +654,10 @@ export function AddProjectSourceScreen() { } function openNewTaskDraft( - navigation: { dispatch: (action: ReturnType) => void }, + navigation: { + dispatch: (action: ReturnType) => void; + getState: () => NavigationState | undefined; + }, params: { environmentId: EnvironmentId; projectId: ProjectId; @@ -657,9 +666,14 @@ function openNewTaskDraft( draftId?: string; }, ) { - navigation.dispatch( - CommonActions.reset({ index: 0, routes: [{ name: "NewTaskDraft", params }] }), - ); + const state = navigation.getState(); + if (!state) return; + const threadIndex = state.routes.findLastIndex((route) => route.name === "Thread"); + const routes = [ + ...state.routes.slice(0, threadIndex < 0 ? state.index : threadIndex), + { key: uuidv4(), name: "Thread", params: { draft: params } }, + ]; + navigation.dispatch(CommonActions.reset({ ...state, index: routes.length - 1, routes })); } async function draftWithPrompt( @@ -698,21 +712,11 @@ function useCreateProject(environment: EnvironmentOption | null) { }); if (existing) { Dialog.alert("Project already exists", existing.title); - navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [ - { - name: "NewTaskDraft", - params: { - environmentId: existing.environmentId, - projectId: existing.id, - title: existing.title, - }, - }, - ], - }), - ); + openNewTaskDraft(navigation, { + environmentId: existing.environmentId, + projectId: existing.id, + title: existing.title, + }); return; } @@ -729,21 +733,11 @@ function useCreateProject(environment: EnvironmentOption | null) { if (AsyncResult.isFailure(result)) { return result; } - navigation.dispatch( - CommonActions.reset({ - index: 0, - routes: [ - { - name: "NewTaskDraft", - params: { - environmentId: environment.environmentId, - projectId, - title: inferProjectTitleFromPath(workspaceRoot), - }, - }, - ], - }), - ); + openNewTaskDraft(navigation, { + environmentId: environment.environmentId, + projectId, + title: inferProjectTitleFromPath(workspaceRoot), + }); return result; }; } diff --git a/apps/mobile/src/features/prompt-links/usePromptLinkResolver.ts b/apps/mobile/src/features/prompt-links/usePromptLinkResolver.ts index 4fa783d0be9e9..2673a6ff20825 100644 --- a/apps/mobile/src/features/prompt-links/usePromptLinkResolver.ts +++ b/apps/mobile/src/features/prompt-links/usePromptLinkResolver.ts @@ -131,9 +131,11 @@ export function usePromptLinkResolver(params: Readonly> } const modelUnavailable = writeDraft(draftKey, project.environmentId, request); navigation.dispatch( - StackActions.push("NewTaskDraft", { - draftId: draftKey, - title: request.pullRequest ? `PR #${request.pullRequest.number}` : project.title, + StackActions.push("Thread", { + draft: { + draftId: draftKey, + title: request.pullRequest ? `PR #${request.pullRequest.number}` : project.title, + }, }), ); if (request.prompt || pullRequestUnavailable !== null || modelUnavailable) { diff --git a/apps/mobile/src/features/pull-requests/PullRequestReviewersScreen.tsx b/apps/mobile/src/features/pull-requests/PullRequestReviewersScreen.tsx index 4f9b9316d434d..af471abf2c869 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestReviewersScreen.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestReviewersScreen.tsx @@ -20,6 +20,7 @@ import { ScrollViewMarker } from "react-native-screens"; import { AppText as Text, AppTextInput as TextInput } from "../../components/ui/AppText"; import { EmptyState } from "../../components/ui/EmptyState"; import { Button } from "../../components/ui/Button"; +import { useRestingTopOffset } from "../../native/useRestingTopOffset"; import { SymbolView } from "../../components/ui/AppSymbol"; import { NativeStackScreenOptions } from "../../native/StackHeader"; import { useNativeLayoutMetrics } from "../../native/native-layout-metrics"; @@ -35,14 +36,15 @@ import { pullRequestTarget, type PullRequestParams } from "./usePullRequestDiff" function ReviewerScrollView(props: ScrollViewProps) { const insets = useSafeAreaInsets(); + const restingOffset = useRestingTopOffset(insets.top, props); return ( ); diff --git a/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx b/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx index 20c85f7189300..a513fd424ced1 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx @@ -56,6 +56,7 @@ import { View, } from "react-native"; import { NativeScrollViewMarker as ScrollViewMarker } from "../../native/NativeScrollViewMarker"; +import { useRestingTopOffset } from "../../native/useRestingTopOffset"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AppText as Text } from "../../components/ui/AppText"; @@ -117,13 +118,14 @@ const EMPTY_IDS: readonly string[] = []; function PullRequestTabScrollView(props: ScrollViewProps) { const insets = useSafeAreaInsets(); + const restingOffset = useRestingTopOffset(insets.top, props); return ( ); @@ -558,7 +560,9 @@ export function PullRequestRouteScreen({ route }: StaticScreenProps { const offset = event.nativeEvent.contentOffset.y; scrollOffsets.current[tab] = offset; diff --git a/apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx b/apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx index e0b97485551c2..ea287c16cf8a5 100644 --- a/apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx +++ b/apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx @@ -21,6 +21,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AppText as Text } from "../../components/ui/AppText"; import { EmptyState } from "../../components/ui/EmptyState"; +import { useRestingTopOffset } from "../../native/useRestingTopOffset"; import { NativePrimaryColumnContext } from "../../native/v5-workspace-context"; import { useWorkspaceState } from "../../state/workspace"; import { useProjects } from "../../state/entities"; @@ -46,13 +47,14 @@ import { useAndroidWorkspaceSelection } from "../workspace/useAndroidWorkspaceSe function PullRequestScrollView(props: ScrollViewProps) { const insets = useSafeAreaInsets(); + const restingOffset = useRestingTopOffset(insets.top, props); return ( ); diff --git a/apps/mobile/src/features/pull-requests/usePullRequestThreads.ts b/apps/mobile/src/features/pull-requests/usePullRequestThreads.ts index 225db0147497f..e845c960e58ac 100644 --- a/apps/mobile/src/features/pull-requests/usePullRequestThreads.ts +++ b/apps/mobile/src/features/pull-requests/usePullRequestThreads.ts @@ -146,9 +146,8 @@ export function usePullRequestThreads( previousPrompts.current.set(draftKey, pullRequestAgentPrompt(task)); if (thread) openThread(thread); else - navigation.navigate("NewTaskSheet", { - screen: "NewTaskDraft", - params: { draftId: draftKey, title: `Ask about #${pr.number}` }, + navigation.navigate("Thread", { + draft: { draftId: draftKey, title: `Ask about #${pr.number}` }, }); }); pending.current = false; diff --git a/apps/mobile/src/features/review/ReviewSheet.tsx b/apps/mobile/src/features/review/ReviewSheet.tsx index 5660301371d7d..3dd9667f9b5a5 100644 --- a/apps/mobile/src/features/review/ReviewSheet.tsx +++ b/apps/mobile/src/features/review/ReviewSheet.tsx @@ -30,6 +30,7 @@ import { } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useNativeColumnLayoutMetrics } from "../../native/native-layout-metrics"; +import { NativeInspectorContent } from "../../native/NativeInspectorContent"; import { AppText as Text } from "../../components/ui/AppText"; import { SymbolView } from "../../components/ui/AppSymbol"; @@ -40,7 +41,6 @@ import { environmentCatalog } from "../../connection/catalog"; import { useEnvironmentPresentation } from "../../state/presentation"; import { useAtomCommand } from "../../state/use-atom-command"; import { useUniwindTheme } from "../../lib/useUniwindTheme"; -import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; @@ -307,7 +307,6 @@ function ReviewFileNavigator({ onSelectFile, ref, }: ReviewFileNavigatorProps) { - const insets = useSafeAreaInsets(); const theme = useUniwindTheme(); const sheetColor = theme["--color-sheet"]; const foregroundColor = theme["--color-foreground"]; @@ -359,15 +358,8 @@ function ReviewFileNavigator({ data={files} extraData={selectedFileId} keyExtractor={(file) => file.id} - contentContainerStyle={{ - paddingHorizontal: 8, - paddingBottom: 8, - - paddingTop: Platform.OS === "ios" ? insets.top + IOS_NAV_BAR_HEIGHT + 8 : 8, - }} - scrollIndicatorInsets={ - Platform.OS === "ios" ? { top: insets.top + IOS_NAV_BAR_HEIGHT } : undefined - } + contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"} + contentContainerStyle={{ paddingHorizontal: 8, paddingVertical: 8 }} renderItem={renderFile} /> ); @@ -384,7 +376,7 @@ function ReviewFileNavigator({ scrollEdgeEffects={headerScrollEdgeEffects} style={{ backgroundColor: sheetColor, flex: 1 }} > - {fileList} + {fileList} [ withNativeGlassHeaderItem({ - axisBehavior: "horizontalOnly", accessibilityLabel: "Filter settings environments and projects", icon: { name: filterIcon, type: "sfSymbol" }, label: "", @@ -121,7 +120,6 @@ export function SettingsEnvironmentFilterHeader(props: { ...(closeSettings ? [ withNativeGlassHeaderItem({ - axisBehavior: "horizontalOnly", accessibilityLabel: "Close settings", icon: { name: "xmark", type: "sfSymbol" }, identifier: "settings-close", diff --git a/apps/mobile/src/features/threads/ChatGptUsageLimitNotice.tsx b/apps/mobile/src/features/threads/ChatGptUsageLimitNotice.tsx new file mode 100644 index 0000000000000..faebfa5ddf927 --- /dev/null +++ b/apps/mobile/src/features/threads/ChatGptUsageLimitNotice.tsx @@ -0,0 +1,69 @@ +import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; +import { + CHATGPT_USAGE_LIMIT_CODE, + CHATGPT_USAGE_URL, + CHATGPT_WORKSPACE_ADMIN_REQUEST, + CHATGPT_WORKSPACE_USAGE_LIMIT_CODE, +} from "@t3tools/shared/usageLimits"; +import * as Clipboard from "expo-clipboard"; +import { useState } from "react"; +import { Linking, View } from "react-native"; +import { ProviderIcon } from "../../components/ProviderIcon"; +import { AppText as Text } from "../../components/ui/AppText"; +import { Button } from "../../components/ui/Button"; + +export function ChatGptUsageLimitNotice({ thread }: { thread: EnvironmentThreadShell }) { + const [status, setStatus] = useState<"copied" | "copy-failed" | "open-failed" | null>(null); + const code = thread.runtime?.lastErrorCode; + const workspaceLimit = code === CHATGPT_WORKSPACE_USAGE_LIMIT_CODE; + if (thread.runtime?.status !== "failed" || (!workspaceLimit && code !== CHATGPT_USAGE_LIMIT_CODE)) + return null; + return ( + + + + ChatGPT usage limit reached + + + {workspaceLimit + ? status === "copied" + ? "Request copied. Send it to your workspace admin." + : "You've reached your workspace's limit for using your ChatGPT plan in other apps. Copy a request to ask your workspace admin for increased usage limits." + : "Review your usage settings in ChatGPT."} + + {workspaceLimit ? ( + + ) : chatGptLimit ? ( + + ) : null} {onDismiss ? ( + setExpanded((value) => !value)} + /> {bodyMounted ? ( // `beforematch` does not bubble, so the reveal listener sits on the hidden fold itself.
void; +}) { + const Icon = props.icon; + return ( + + ); +} + function UserMessageProjectedStatus({ row }: { row: Extract }) { const status = row.projectedItem?.item.status; if ( @@ -278,6 +300,15 @@ function UserTimelineRow({ row }: { row: Extract !isImageAttachment(attachment) && !isFileAttachment(attachment), ); const userMessage = resolveUserMessagePresentation(row.message); + // Messages from other agents fold to one line so the user's own messages stay easy to find. + const folded = userMessage.attribution === "agent"; + const [expanded, setExpanded] = useState(false); + const revealFoldRef = findRevealRef(() => setExpanded(true)); + const senderShell = useQuietThreadShell( + folded && senderThreadId + ? { environmentId: ctx.activeThreadEnvironmentId, threadId: senderThreadId } + : null, + ); const resolvedContext = resolveUserMessageContext(row.message); const previewImages = userImages.filter((image) => image.name.startsWith("preview-annotation-")); const revertTurnCount = row.revertTurnCount; @@ -297,6 +328,11 @@ function UserTimelineRow({ row }: { row: Extract reference.label, ); const promptLink = promptLinkFor(plainText); + const foldSummary = + plainText + .split(/\r?\n/) + .find((line) => line.trim().length > 0) + ?.trim() ?? ""; const contextClipboardFragment = resolvedContext.records.length === 0 ? null @@ -385,7 +421,10 @@ function UserTimelineRow({ row }: { row: Extract +
{isHomeReportMessageId(row.message.id) ? (

Sent by T3 Code

- ) : userMessage.attribution === "agent" ? ( -

- {senderThreadId ? ( - ctx.onOpenThread(senderThreadId)} - tone="muted" - aria-label="Open sending thread" - > - {isHomeThreadId(senderThreadId) ? "Sent by Home" : "Sent by another agent"} - - ) : ( - "Sent by another agent" - )} -

) : null} {row.message.inputIntent === "steer" || row.message.inputIntent === "promoted_queued_to_steer" ? ( ) : null} -
- You - {(regularImages.length > 0 || userVideos.length > 0) && ( -
- {regularImages.map((image) => ( -
- {image.previewUrl ? ( - - ) : ( -
- {image.name} + {image.previewUrl ? ( + + ) : ( +
+ {image.name} +
+ )} + {image.previewUrl && image.source?.kind === "snap-shot" ? ( + + ) : null}
- )} - {image.previewUrl && image.source?.kind === "snap-shot" ? ( - - ) : null} + ))} + {userVideos.map((file) => ( + + ))}
- ))} - {userVideos.map((file) => ( - - ))} -
- )} - {unchippedFiles.length > 0 || unknownAttachments.length > 0 ? ( -
- {unchippedFiles.map((file) => { - const fileIdentity = ( - <> - - {file.name} - - ); - if (file.downloadable !== false) { - return ( -
- + + ctx.onFileDownload(file)} + /> + } + > + + + Download {file.name} + +
+ ); + } + + return ( +
{fileIdentity} - - - - ctx.onFileDownload(file)} - /> - } - > - - - Download {file.name} - +
+ ); + })} + {unknownAttachments.map((attachment) => ( +
+ + {attachment.name}
- ); - } - - return ( -
- {fileIdentity} -
- ); - })} - {unknownAttachments.map((attachment) => ( -
- - {attachment.name} + ))}
- ))} + ) : null} +
+ +
) : null} -
- -
-
-
- - }> - {formatDayAwareTimestamp(row.message.createdAt, ctx.timestampFormat)} - - - {formatChatTimestampTooltip(row.message.createdAt, ctx.timestampFormat)} - - -
- {typeof revertTurnCount === "number" && ( - - )} - {resolvedContext.text && ( - - )} - {promptLink !== null && ( - - )} + {!folded || expanded ? ( +
+
+ {folded && senderThreadId ? ( + ctx.onOpenThread(senderThreadId)} tone="muted"> + Open thread + + ) : null} + + }> + {formatDayAwareTimestamp(row.message.createdAt, ctx.timestampFormat)} + + + {formatChatTimestampTooltip(row.message.createdAt, ctx.timestampFormat)} + + +
+ {typeof revertTurnCount === "number" && ( + + )} + {resolvedContext.text && ( + + )} + {promptLink !== null && ( + + )} +
-
+ ) : null}
); } @@ -643,13 +712,15 @@ const CollapsibleUserMessageBody = memo(function CollapsibleUserMessageBody(prop renderContextReference: (reference: ChatMarkdownContextReference) => ReactNode; skills: ReadonlyArray>; markdownCwd: string | undefined; + collapsible?: boolean; footer?: ReactNode; }) { const [expanded, setExpanded] = useState(false); const revealForFind = () => setExpanded(true); const revealRef = findRevealRef(revealForFind); const hasVisibleBody = props.text.trim().length > 0; - const canCollapse = hasVisibleBody && shouldCollapseUserMessage(props.text); + const canCollapse = + props.collapsible !== false && hasVisibleBody && shouldCollapseUserMessage(props.text); const isCollapsed = canCollapse && !expanded; const showCollapseControl = canCollapse; diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx index 11b56a19038c7..07d6164817c19 100644 --- a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx +++ b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx @@ -330,12 +330,12 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap variant="ghost-muted" size="micro" className="ml-auto min-w-0 shrink-0" - aria-label={`${scriptName} is still running. Show setup progress.`} + aria-label={`Background setup is still running: ${scriptName}. Show setup progress.`} /> } > - {scriptName} + Background setup ({ type: "text", value }); +const element = (tagName: string, children: object[]) => ({ + type: "element", + tagName, + properties: {} as Record, + children, +}); + +function directed(...blocks: ReturnType[]) { + rehypeTextDirection()({ type: "root", children: blocks }); + return blocks; +} + +describe("rehypeTextDirection", () => { + it("keeps raw HTML list items apart when counting words", () => { + const [list] = directed( + element("ul", [element("li", [text("مرحبا")]), element("li", [text("hello world")])]), + ); + expect(list!.properties.dir).toBe("ltr"); + }); + + it("leaves inline code out of the count and isolates it", () => { + const code = element("code", [text("useEffect useLayoutEffect React")]); + const [paragraph] = directed(element("p", [text("استخدم "), code, text(" هنا")])); + expect(paragraph!.properties.dir).toBe("rtl"); + expect(code.properties.dir).toBe("ltr"); + }); + + it("leaves code blocks to their own left-to-right frame", () => { + const code = element("code", [text("// احسب المجموع هنا")]); + const [pre] = directed(element("pre", [code])); + expect(pre!.properties.dir).toBeUndefined(); + expect(code.properties.dir).toBeUndefined(); + }); +}); diff --git a/apps/web/src/components/chat/markdownTextDirection.ts b/apps/web/src/components/chat/markdownTextDirection.ts new file mode 100644 index 0000000000000..ffdbde76c1148 --- /dev/null +++ b/apps/web/src/components/chat/markdownTextDirection.ts @@ -0,0 +1,67 @@ +import { textDirection } from "~/textDirection"; + +type HastNode = { + type?: string; + tagName?: string; + value?: string; + properties?: Record; + children?: HastNode[]; +}; + +// Raw HTML such as `
  • a
  • b
  • ` has no whitespace between its +// blocks, so they add their own to keep the words apart. +const BLOCK_TAGS = new Set([ + "blockquote", + "br", + "details", + "div", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "li", + "ol", + "p", + "summary", + "table", + "td", + "th", + "tr", + "ul", +]); + +function proseText(node: HastNode): string { + if (node.type === "text") return node.value ?? ""; + if (node.tagName === "code") return " "; + const text = (node.children ?? []).map(proseText).join(""); + return node.tagName && BLOCK_TAGS.has(node.tagName) ? ` ${text} ` : text; +} + +function isolateInlineCode(node: HastNode): void { + if (node.tagName === "pre") return; + if (node.tagName === "code") { + node.properties = { ...node.properties, dir: "ltr" }; + return; + } + node.children?.forEach(isolateInlineCode); +} + +/** + * Gives each top-level block the direction of its own words, so an Arabic or + * Hebrew reply reads right to left beside English ones. Lines inside a list or + * quote follow it, keeping markers and rules on one side. Inline code is left + * out of the count and isolated left to right, so its punctuation stays put. + */ +export function rehypeTextDirection() { + return (tree: HastNode) => { + for (const block of tree.children ?? []) { + if (block.type !== "element" || block.tagName === "pre") continue; + const dir = textDirection(proseText(block)); + if (dir) block.properties = { ...block.properties, dir }; + } + isolateInlineCode(tree); + }; +} diff --git a/apps/web/src/components/chat/useAttachmentPreviewHandoff.ts b/apps/web/src/components/chat/useAttachmentPreviewHandoff.ts index 4012c7f1e559c..4556a3180c286 100644 --- a/apps/web/src/components/chat/useAttachmentPreviewHandoff.ts +++ b/apps/web/src/components/chat/useAttachmentPreviewHandoff.ts @@ -3,12 +3,12 @@ import { type EnvironmentId, type MessageId } from "@t3tools/contracts"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { selectHandoffImageResources } from "../../session-logic"; import { type ChatMessage } from "../../types"; -import type { useThreadVisibleTurnItems } from "../../state/entities"; +import type { useQuietThreadVisibleTurnItems } from "../../state/entities"; import { revokeBlobPreviewUrl, revokeUserMessagePreviewUrls } from "../ChatView.logic"; import { useAssetUrls } from "../../assets/assetUrls"; interface AttachmentPreviewHandoffInput { readonly environmentId: EnvironmentId; - readonly serverVisibleTurnItems: ReturnType; + readonly serverVisibleTurnItems: ReturnType; readonly optimisticUserMessages: ReadonlyArray; } diff --git a/apps/web/src/components/chat/useChatShortcuts.browser.test.tsx b/apps/web/src/components/chat/useChatShortcuts.browser.test.tsx new file mode 100644 index 0000000000000..b601de299dac5 --- /dev/null +++ b/apps/web/src/components/chat/useChatShortcuts.browser.test.tsx @@ -0,0 +1,96 @@ +import { useEffect } from "react"; +import { expect, it } from "vite-plus/test"; +import { userEvent } from "vite-plus/test/browser"; + +import { render } from "../../../test/render"; +import type { ChatComposerHandle } from "./ChatComposer"; +import { useChatShortcuts } from "./useChatShortcuts"; + +const shortcutContext = { + terminalFocus: false, + terminalOpen: false, + previewFocus: false, + previewOpen: false, + isWeb: true, + isDesktop: false, + modelPickerOpen: false, +}; + +const focusOnMount = (element: HTMLElement | null) => element?.focus(); + +// Stands in for the right panel's launcher, which needs the whole app around it. Like the +// real one it takes focus, advertises its letters in `data-surface-launcher-keys`, and +// claims one from its own window capture listener unless the key was already handled. +function Launcher(props: { keys: string; received: string[] }) { + useEffect(() => { + const claim = (event: KeyboardEvent) => { + if (event.defaultPrevented) return; + if (!props.keys.toLowerCase().split("").includes(event.key.toLowerCase())) return; + event.preventDefault(); + props.received.push(event.key); + }; + window.addEventListener("keydown", claim, true); + return () => window.removeEventListener("keydown", claim, true); + }, [props.keys, props.received]); + return ( +
    + ); +} + +// The chat's shortcut listener registers when the chat mounts, so a launcher that opens +// later registers after it. Tests mount the chat first and add the launcher by rerendering. +function Chat(props: { composerKeys: string[]; launcher?: { keys: string; received: string[] } }) { + useChatShortcuts({ + enabled: true, + keybindings: [], + scriptKeybindings: [], + getShortcutContext: () => shortcutContext, + threadFindActive: false, + // Type-to-focus only calls insertTextAtEnd, so the stand-in composer implements just that. + composerRef: { + current: { + insertTextAtEnd: (key: string) => { + props.composerKeys.push(key); + return true; + }, + } as Partial as ChatComposerHandle, + }, + branchToolbarRef: { current: null }, + closeThreadFind: () => {}, + commands: {}, + fallback: () => undefined, + }); + return props.launcher ? : null; +} + +// router: false because the chat shortcuts and the launcher stand-in never navigate. +const options = { router: false } as const; + +it("leaves the launcher's letters to the launcher, in either case", async () => { + const composerKeys: string[] = []; + const received: string[] = []; + await using screen = await render(, options); + await screen.rerender(); + + await userEvent.keyboard("tT"); + + expect(received).toEqual(["t", "T"]); + expect(composerKeys).toEqual([]); +}); + +it("still types other letters into the composer while the launcher is open", async () => { + const composerKeys: string[] = []; + const received: string[] = []; + await using screen = await render(, options); + await screen.rerender(); + + await userEvent.keyboard("a"); + + expect(composerKeys).toEqual(["a"]); + expect(received).toEqual([]); +}); diff --git a/apps/web/src/components/chat/useComposerSelection.ts b/apps/web/src/components/chat/useComposerSelection.ts index b2666c32a6fd1..61992327d3610 100644 --- a/apps/web/src/components/chat/useComposerSelection.ts +++ b/apps/web/src/components/chat/useComposerSelection.ts @@ -12,7 +12,7 @@ import { ProviderInteractionMode, RuntimeMode, } from "@t3tools/contracts"; -import { deriveThreadRuntime } from "@t3tools/client-runtime/state/thread-execution"; +import type { deriveThreadStructureRuntime } from "@t3tools/client-runtime/state/thread-execution"; import { scopeThreadRef } from "@t3tools/client-runtime/environment"; import { type SetStateAction } from "react"; import { useNavigate } from "@tanstack/react-router"; @@ -30,7 +30,7 @@ interface ComposerSelectionInput { readonly composerDraftTarget: ComposerThreadTarget; readonly draftThread: DraftSessionState | null; readonly activeThread: EnvironmentThreadShell | undefined; - readonly activeRuntime: ReturnType; + readonly activeRuntime: ReturnType; readonly activeProjectSettings: { readonly settings: { readonly newWorktreesStartFromOrigin: boolean }; }; diff --git a/apps/web/src/components/chat/usePendingThreadRequests.ts b/apps/web/src/components/chat/usePendingThreadRequests.ts index 9ce6172a28d59..3b1e73e593898 100644 --- a/apps/web/src/components/chat/usePendingThreadRequests.ts +++ b/apps/web/src/components/chat/usePendingThreadRequests.ts @@ -1,7 +1,7 @@ +import type { ThreadStructure } from "@t3tools/client-runtime/state/shell"; import { AuthOrchestrationOperateScope, type EnvironmentId, - type OrchestrationV2ThreadProjection, type ProviderApprovalDecision, type RuntimeRequestId, type ScopedThreadRef, @@ -110,7 +110,7 @@ export function usePendingThreadRequests(input: { readonly environmentId: EnvironmentId; readonly activeThreadId: ThreadId | null; readonly activeThread: { readonly id: ThreadId } | undefined; - readonly serverProjection: OrchestrationV2ThreadProjection | null; + readonly serverProjection: ThreadStructure | null; readonly supportsQuestionAttachments: boolean; readonly composerDraftTarget: ScopedThreadRef | DraftId; readonly setComposerDraftPrompt: (target: ScopedThreadRef | DraftId, prompt: string) => void; diff --git a/apps/web/src/components/chat/useQueuedRunEdit.ts b/apps/web/src/components/chat/useQueuedRunEdit.ts index 0d5b87a9398f0..4ac1af47bf3c6 100644 --- a/apps/web/src/components/chat/useQueuedRunEdit.ts +++ b/apps/web/src/components/chat/useQueuedRunEdit.ts @@ -1,10 +1,10 @@ +import type { ThreadStructure } from "@t3tools/client-runtime/state/shell"; import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS, type ChatAttachment, type EnvironmentId, type MessageId, type OrchestrationMessageContext, - type OrchestrationV2ThreadProjection, type PreviewAnnotationPayload, type RunId, type ScopedThreadRef, @@ -170,7 +170,7 @@ export function useQueuedRunEdit(input: { readonly threadDraftTarget: ComposerThreadTarget; // Null while the route has no thread yet; an edit needs one to target. readonly activeThreadId: ThreadId | null; - readonly serverProjection: OrchestrationV2ThreadProjection | null; + readonly serverProjection: ThreadStructure | null; readonly promptRef: { current: string }; readonly composerRef: RefObject; }) { diff --git a/apps/web/src/components/chat/useThreadFind.ts b/apps/web/src/components/chat/useThreadFind.ts index 1ff36b327052f..58b1f710dfb3a 100644 --- a/apps/web/src/components/chat/useThreadFind.ts +++ b/apps/web/src/components/chat/useThreadFind.ts @@ -1,13 +1,10 @@ import type { InlineSkill } from "@t3tools/shared/inlineSkills"; -import type { - OrchestrationV2ThreadProjection, - OrchestrationV2SearchThreadResult, - ScopedThreadRef, -} from "@t3tools/contracts"; +import type { OrchestrationV2SearchThreadResult, ScopedThreadRef } from "@t3tools/contracts"; import { scopedThreadKey } from "@t3tools/client-runtime/environment"; import { useEffect, useLayoutEffect, useRef, useState, type RefObject } from "react"; import { useStableCallback } from "~/hooks/useStableCallback"; import { orchestrationEnvironment } from "~/state/orchestration"; +import { useThreadProjection } from "~/state/entities"; import { useEnvironmentQuery } from "~/state/query"; import { useDebouncedValue } from "~/state/queries"; import { @@ -42,14 +39,15 @@ export function useThreadFind({ enabled, progressive = false, skills = EMPTY_SKILLS, - content, }: { thread: ScopedThreadRef | null; enabled: boolean; progressive?: boolean; skills?: readonly InlineSkill[]; - content: Pick | undefined; }) { + // Read here rather than passed in: a streamed delta re-renders only the find + // provider, which refreshes open results from it. + const content = useThreadProjection(enabled ? thread : null)?.projection; // Held in state so the timeline props that carry it can be stored in state too. const [positionReader] = useState>(() => ({ current: null, diff --git a/apps/web/src/components/chat/useTimelineFollow.ts b/apps/web/src/components/chat/useTimelineFollow.ts index f0a96742c3d2e..191b1d2e8ea48 100644 --- a/apps/web/src/components/chat/useTimelineFollow.ts +++ b/apps/web/src/components/chat/useTimelineFollow.ts @@ -31,7 +31,7 @@ import { resolveTimelineIsAtEnd } from "./MessagesTimeline.logic"; import { shouldReleaseTimelineAnchorForToolActivity } from "../ChatView.logic"; import { releaseChatTimelineAnchor } from "../ChatView.logic"; import type { Dispatch, RefObject } from "react"; -import type { EnvironmentThread } from "@t3tools/client-runtime/state/shell"; +import type { ThreadStructure } from "@t3tools/client-runtime/state/shell"; import type { deriveThreadActivityRun } from "@t3tools/client-runtime/state/thread-execution"; import type { EnvironmentThreadStatus } from "@t3tools/client-runtime/state/threads"; import type { TimelineEntry } from "../../session-logic"; @@ -52,7 +52,7 @@ interface TimelineFollowInput { readonly activeThreadKey: string | null; readonly activeThreadId: ThreadId | undefined; readonly isServerThread: boolean; - readonly serverProjection: EnvironmentThread["projection"] | null; + readonly serverProjection: ThreadStructure | null; readonly threadStatus: EnvironmentThreadStatus; readonly activeActivityRun: ReturnType; readonly activeRunningTurnId: RunId | null; @@ -586,9 +586,17 @@ export function useTimelineFollow(input: TimelineFollowInput) { }; const onIsAtEndChangeStable = useStableCallback(onIsAtEndChange); + // timelineEntries leave out reasoning rows (their text is not in the + // structure), and reasoning counts as tool activity, like any work row. + const runningTurnHasReasoning = + activeRunningTurnId !== null && + serverProjection?.visibleTurnItems.some( + (row) => row.item.type === "reasoning" && row.item.runId === activeRunningTurnId, + ) === true; useLayoutEffect(() => { if (timelineScrollModeRef.current !== "anchoring-new-turn") return; if ( + (runningTurnHasReasoning && timelineAnchorMessageId !== null && timelineLiveFollowEnabled) || shouldReleaseTimelineAnchorForToolActivity({ anchorMessageId: timelineAnchorMessageId, liveFollowEnabled: timelineLiveFollowEnabled, @@ -600,6 +608,7 @@ export function useTimelineFollow(input: TimelineFollowInput) { } }, [ activeRunningTurnId, + runningTurnHasReasoning, scrollToEndStable, timelineAnchorMessageId, timelineEntries, diff --git a/apps/web/src/components/chat/useTimelineRewind.ts b/apps/web/src/components/chat/useTimelineRewind.ts index 6f9e042951b47..6c7aef0a75693 100644 --- a/apps/web/src/components/chat/useTimelineRewind.ts +++ b/apps/web/src/components/chat/useTimelineRewind.ts @@ -1,5 +1,4 @@ -import type { EnvironmentThread } from "@t3tools/client-runtime/state/shell"; -import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; +import type { EnvironmentThreadShell, ThreadStructure } from "@t3tools/client-runtime/state/shell"; import type { ComposerThreadTarget } from "../../composerDraftStore"; import type { ComposerHandleRef } from "../../composerHandleContext"; import type { SessionPhase } from "../../types"; @@ -39,7 +38,7 @@ interface TimelineRewindInput { readonly routeThreadRef: ScopedThreadRef; readonly routeThreadKey: string; readonly currentRouteThreadKeyRef: { readonly current: string | null }; - readonly serverProjection: EnvironmentThread["projection"] | null; + readonly serverProjection: ThreadStructure | null; readonly activeThread: EnvironmentThreadShell | undefined; readonly timelineMessages: ReadonlyArray; readonly paintOnlyDisplayedTimeline: boolean; diff --git a/apps/web/src/components/chat/useWorktreeSetup.ts b/apps/web/src/components/chat/useWorktreeSetup.ts index 6f8cb1269d525..ed38ad3ef59dd 100644 --- a/apps/web/src/components/chat/useWorktreeSetup.ts +++ b/apps/web/src/components/chat/useWorktreeSetup.ts @@ -1,6 +1,6 @@ +import type { ThreadStructure } from "@t3tools/client-runtime/state/shell"; import type { EnvironmentId, - OrchestrationV2ThreadProjection, RunId, ScopedThreadRef, ThreadId, @@ -34,7 +34,7 @@ export function useWorktreeSetup(input: { | { readonly id: ThreadId; readonly worktreePath: string | null } | undefined; readonly threadRef: ScopedThreadRef | null; - readonly serverProjection: OrchestrationV2ThreadProjection | null; + readonly serverProjection: ThreadStructure | null; readonly activityRun: Thread["latestRun"] | null; readonly localPreparing: boolean; // A send that the server queues behind the setup run is not held here. diff --git a/apps/web/src/components/files/AudioPreview.tsx b/apps/web/src/components/files/AudioPreview.tsx index 2357c5466b6c0..23d0d2db15c3f 100644 --- a/apps/web/src/components/files/AudioPreview.tsx +++ b/apps/web/src/components/files/AudioPreview.tsx @@ -1,17 +1,39 @@ +import { useState } from "react"; + export function AudioPreview(props: { readonly src: string; readonly name: string; + /** Re-signed URLs with the same revision keep the loaded audio and its position. */ + readonly revision?: string | null; readonly onError?: () => void; }) { + const [loadedSource, setLoadedSource] = useState<{ src: string; revision: string } | null>(null); + const [playing, setPlaying] = useState(false); + const revision = props.revision ?? null; + // Changed files load once playback pauses. + if (loadedSource !== null && loadedSource.revision !== revision && !playing) { + setLoadedSource(revision === null ? null : { src: props.src, revision }); + } + const src = loadedSource?.src ?? props.src; return (
    ); diff --git a/apps/web/src/components/files/FilePreviewPanel.tsx b/apps/web/src/components/files/FilePreviewPanel.tsx index 414a5e6e41389..a0ba43b931ca8 100644 --- a/apps/web/src/components/files/FilePreviewPanel.tsx +++ b/apps/web/src/components/files/FilePreviewPanel.tsx @@ -289,10 +289,15 @@ function WorkspaceVideoPreview(props: { void refreshAssetUrl().catch(() => undefined); }, }); + // Servers that report the file's revision let an unchanged file stay loaded; older ones + // only offer the workspace mutation, which changes after every command. The revision also + // goes in the URL, since two mints in the same millisecond sign identical URLs. + const fileRevision = assetUrl._tag === "Success" ? assetUrl.fileRevision : undefined; + const revision = fileRevision ?? props.workspaceMutationId; const revisionSuffix = - props.workspaceMutationId === null + revision === null ? "" - : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}workspace-revision=${encodeURIComponent(props.workspaceMutationId)}`; + : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}${fileRevision !== undefined ? "file" : "workspace"}-revision=${encodeURIComponent(revision)}`; const latestUrl = assetUrl._tag === "Success" ? `${assetUrl.url}${revisionSuffix}` : null; return ( @@ -301,7 +306,7 @@ function WorkspaceVideoPreview(props: { src={latestUrl} sourceFailed={assetUrl._tag === "Failure"} label={props.name} - revision={props.workspaceMutationId} + revision={revision} preload="metadata" className="flex h-full min-h-0 w-full max-w-5xl items-center justify-center" onRetry={refreshAssetUrl} @@ -343,10 +348,12 @@ function WorkspaceAudioPreview(props: { void refreshAssetUrl().catch(() => undefined); }, }); + const fileRevision = assetUrl._tag === "Success" ? assetUrl.fileRevision : undefined; + const revision = fileRevision ?? props.workspaceMutationId; const revisionSuffix = - props.workspaceMutationId === null + revision === null ? "" - : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}workspace-revision=${encodeURIComponent(props.workspaceMutationId)}`; + : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}${fileRevision !== undefined ? "file" : "workspace"}-revision=${encodeURIComponent(revision)}`; const url = assetUrl._tag === "Success" ? `${assetUrl.url}${revisionSuffix}` : null; if (assetUrl._tag === "Failure" || (url !== null && failedUrl === url)) { return ( @@ -360,7 +367,14 @@ function WorkspaceAudioPreview(props: { ); } if (url === null) return ; - return setFailedUrl(url)} />; + return ( + setFailedUrl(url)} + /> + ); } function clampFileLine(contents: string, requestedLine: number): number { diff --git a/apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx b/apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx new file mode 100644 index 0000000000000..d335d1b36e7f7 --- /dev/null +++ b/apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { act, render, stubGlobal, type Screen } from "../../../test/render"; +import { MediaVideoPlayer } from "./MediaVideoPlayer"; + +// The observer never reports the video as visible, so it keeps preload="none" and never +// requests these fake URLs; the test fires the media events itself. +class HiddenIntersectionObserver { + observe() {} + disconnect() {} +} + +function expectVideoSrc(screen: Screen, src: string) { + return expect.element(screen.getByLabelText("clip")).toHaveAttribute("src", src); +} + +async function loadMetadata(screen: Screen) { + await act(() => { + screen.getByLabelText("clip").element().dispatchEvent(new Event("loadedmetadata")); + }); +} + +describe("MediaVideoPlayer", () => { + it("keeps a loaded paused video when only its URL is re-signed", async () => { + using _observer = stubGlobal("IntersectionObserver", HiddenIntersectionObserver); + await using screen = await render( + , + ); + await loadMetadata(screen); + + await screen.rerender( + , + ); + await expectVideoSrc(screen, "/asset/first/clip.mp4"); + + await screen.rerender( + , + ); + await expectVideoSrc(screen, "/asset/changed/clip.mp4"); + + // A second re-sign while the changed file is still loading must not restart that load. + await screen.rerender( + , + ); + await expectVideoSrc(screen, "/asset/changed/clip.mp4"); + }); + + it("keeps a loaded paused video while its URL is briefly unavailable", async () => { + using _observer = stubGlobal("IntersectionObserver", HiddenIntersectionObserver); + await using screen = await render( + , + ); + await loadMetadata(screen); + + await screen.rerender(); + await expectVideoSrc(screen, "/asset/first/clip.mp4"); + }); +}); diff --git a/apps/web/src/components/media/MediaVideoPlayer.tsx b/apps/web/src/components/media/MediaVideoPlayer.tsx index 1392b995c5367..bee45cec5b268 100644 --- a/apps/web/src/components/media/MediaVideoPlayer.tsx +++ b/apps/web/src/components/media/MediaVideoPlayer.tsx @@ -60,13 +60,16 @@ export function MediaVideoPlayer({ const failed = src !== null ? failedSrc === src : sourceFailed; const preloaded = preloadedSrc === src || typeof IntersectionObserver === "undefined"; - // Re-signing must not reset the playhead. Changed files refresh once playback pauses. + // Re-signing must not reload a loaded source. Changed files load once playback pauses. if ( playbackSource !== null && playbackSource.revision !== revision && + (latestSrc !== null || sourceFailed) && (video === null || playingVideo !== video) ) { - setPlaybackSource(null); + setPlaybackSource( + revision !== null && latestSrc !== null ? { src: latestSrc, revision } : null, + ); } useEffect(() => { @@ -174,7 +177,10 @@ export function MediaVideoPlayer({ videoClassName, )} style={style} - onLoadedMetadata={(event) => prepareVideoFirstFrame(event.currentTarget)} + onLoadedMetadata={(event) => { + prepareVideoFirstFrame(event.currentTarget); + if (sourceRevision !== null) setPlaybackSource({ src, revision: sourceRevision }); + }} onPlay={(event) => { setPlayingVideo(event.currentTarget); setPlaybackSource({ src, revision: sourceRevision }); @@ -183,8 +189,8 @@ export function MediaVideoPlayer({ onEnded={() => setPlayingVideo(null)} onError={() => { setPlayingVideo(null); - if (latestSrc !== null && src !== latestSrc) setPlaybackSource(null); - else setFailedSrc(src); + setPlaybackSource(null); + if (latestSrc === null || src === latestSrc) setFailedSrc(src); }} /> ) : ( diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts index d0a4e506b46fb..b4d7a7052163e 100644 --- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts +++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts @@ -292,13 +292,6 @@ export function allowedPullRequestMergeMethods( : detail.capabilities.mergeMethods.filter((method) => detail.mergeCapabilities[method]); } -export function resolveSelectedMergeMethod( - allowedMergeMethods: ReadonlyArray, - preferred: PullRequestMergeMethod, -): PullRequestMergeMethod { - return allowedMergeMethods.includes(preferred) ? preferred : (allowedMergeMethods[0] ?? "merge"); -} - function canPerformPullRequestAction( detail: Pick | null, action: PullRequestAction, diff --git a/apps/web/src/components/pullRequest/usePullRequestActions.ts b/apps/web/src/components/pullRequest/usePullRequestActions.ts index 68effbe5780c9..f851678a27ed4 100644 --- a/apps/web/src/components/pullRequest/usePullRequestActions.ts +++ b/apps/web/src/components/pullRequest/usePullRequestActions.ts @@ -42,7 +42,7 @@ import { pullRequestEntryKey, type EnvironmentPullRequestEntry } from "./pullReq export function usePullRequestDefaultMergeMethodResolver( environmentId: EnvironmentId, - projectId: ProjectId, + projectId: ProjectId | null, ) { const projectDefault = useEnvironmentSettings( environmentId, diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 649f26ee1e7ff..c733fae9353ef 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from "react"; import { type DesktopUpdateChannel, IDLE_COMPACTION_MODES, + globalIdleCompaction, ProviderDriverKind, type ProviderInstanceId, type SidebarProjectGroupingMode, @@ -51,6 +52,7 @@ import { } from "./useScopedSettings"; import { useScopedModelDisabledReason } from "./useScopedModelAvailability"; import { useSettingsScope } from "./SettingsScopeContext"; +import { scopedIdleCompaction } from "./scopedSettings"; import { ProjectDefaultsSettings } from "./ProjectDefaultsSettings"; import { useDesktopUpdateState } from "../../state/desktopUpdate"; import { @@ -785,7 +787,8 @@ export function GeneralSettingsPanel() { const settings = useScopedSettings(); const updateSettings = useUpdateScopedSettings(); const navigate = useNavigate(); - const { scope, environment, connectedEnvironments, target } = useSettingsScope(); + const { scope, environment, connectedEnvironments, target, targets } = useSettingsScope(); + const idleCompaction = scopedIdleCompaction(targets.length > 0 ? targets : [{ settings }]); const environmentId = environment?.environmentId ?? null; const isEnvironmentScope = scope.environmentIds.length === 1 && environmentId !== null; const hasServerTargets = connectedEnvironments.length > 0; @@ -996,40 +999,46 @@ export function GeneralSettingsPanel() { - updateSettings({ - compactIdleThreads: DEFAULT_UNIFIED_SETTINGS.compactIdleThreads, - }) + updateSettings({ idleCompaction: globalIdleCompaction(DEFAULT_UNIFIED_SETTINGS) }) } /> ) : null } control={ onRenameTitleChange(event.target.value)} onFocus={(event) => event.currentTarget.select()} @@ -1023,7 +1025,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP {thread.title} diff --git a/apps/web/src/components/usage/UsagePage.tsx b/apps/web/src/components/usage/UsagePage.tsx index e0ab70d4511cf..4a940a8d3572f 100644 --- a/apps/web/src/components/usage/UsagePage.tsx +++ b/apps/web/src/components/usage/UsagePage.tsx @@ -224,7 +224,9 @@ export function UsagePage() { const refreshingUsage = isRefreshing && !showingLimits; const showingKept = shown !== null && shown.window !== window; const presentations = useAtomValue(environmentPresentations.presentationsAtom); - const providersByEnvironment = useAtomValue(environmentPresentations.providersByEnvironmentAtom); + const presentationsWithProviders = useAtomValue( + environmentPresentations.presentationsWithProvidersAtom, + ); const cursorAccessEnvironments = hiddenProviders.has("cursor") ? [] : cursorKeychainAccessEnvironments(selectedEnvironments); @@ -351,10 +353,10 @@ export function UsagePage() { : enumerateHourStarts(shownWindow.sinceTime, shownWindow.untilTime); const showChatGptSharing = !hiddenProviders.has("codex") && - [...providersByEnvironment].some( - ([id, providers]) => + [...presentationsWithProviders].some( + ([id, presentation]) => (selectedEnvironmentIds === null || selectedEnvironmentIds.has(id)) && - providers.some(usesChatGptSharing), + (presentation.serverConfig?.providers ?? []).some(usesChatGptSharing), ); const windowLabel = diff --git a/apps/web/src/composer-logic.test.ts b/apps/web/src/composer-logic.test.ts index 69d02eb3bf8d0..6bbc30d0ade51 100644 --- a/apps/web/src/composer-logic.test.ts +++ b/apps/web/src/composer-logic.test.ts @@ -266,6 +266,18 @@ describe("detectComposerTrigger", () => { expect(trigger).toBeNull(); }); + it.each([ + "It costs $5", + "budget $9.99", + "only $.99", + "about €20", + "raise $10k", + "raise $10k.", + "is it $5?", + ])("does not detect a skill trigger for the amount in %j", (text) => { + expect(detectComposerTrigger(text, text.length)).toBeNull(); + }); + it("detects a pull request number at a token boundary", () => { const text = "Compare this with #8737"; diff --git a/apps/web/src/composer-logic.ts b/apps/web/src/composer-logic.ts index 7046abd6ed2f9..95e1d73c02201 100644 --- a/apps/web/src/composer-logic.ts +++ b/apps/web/src/composer-logic.ts @@ -9,7 +9,7 @@ import { serializeAssistantCitation, withAssistantCitationComment, } from "@t3tools/shared/assistantCitations"; -import { isPathWord } from "@t3tools/shared/composerTrigger"; +import { isCurrencyAmountQuery, isPathWord } from "@t3tools/shared/composerTrigger"; import { splitPromptIntoComposerSegments, type ComposerPromptSegment, @@ -302,6 +302,7 @@ export function detectComposerTrigger(text: string, cursorInput: number): Compos } const skillPrefix = /^\p{Sc}/u.exec(token); if (skillPrefix) { + if (isCurrencyAmountQuery(token.slice(skillPrefix[0].length))) return null; return { kind: "skill", query: token.slice(skillPrefix[0].length), diff --git a/apps/web/src/composer-rich-text-doc.ts b/apps/web/src/composer-rich-text-doc.ts index db893eb2974a0..e9403e6527df0 100644 --- a/apps/web/src/composer-rich-text-doc.ts +++ b/apps/web/src/composer-rich-text-doc.ts @@ -126,9 +126,14 @@ function codePairCaretPlugin(type: MarkType) { */ export const ComposerCodeExtension = Code.extend({ excludes: "code", - // ArrowRight leaves code through the caret stops at styled edges, so the - // stock exit (inserting a space at the end of a line) is not needed. + // The forward arrow leaves code through the caret stops at styled edges, so + // the stock exit (inserting a space at the end of a line) is not needed. exitable: false, + // Code reads left to right inside an Arabic or Hebrew line, and the isolate + // keeps its punctuation from trading places with the text around it. + addOptions() { + return { ...this.parent!(), HTMLAttributes: { dir: "ltr" } }; + }, addProseMirrorPlugins() { return [...(this.parent?.() ?? []), codePairCaretPlugin(this.type)]; }, diff --git a/apps/web/src/composer-text-direction.test.ts b/apps/web/src/composer-text-direction.test.ts new file mode 100644 index 0000000000000..b8b03fafde476 --- /dev/null +++ b/apps/web/src/composer-text-direction.test.ts @@ -0,0 +1,62 @@ +import { getSchemaByResolvedExtensions, resolveExtensions } from "@tiptap/core"; +import { Node as ProseMirrorNode } from "@tiptap/pm/model"; +import StarterKit from "@tiptap/starter-kit"; +import { describe, expect, it } from "vite-plus/test"; + +import { + buildDocJson, + ComposerBlockExtensions, + ComposerCodeBlockExtension, + ComposerCodeExtension, + ComposerListExtensions, +} from "./composer-rich-text-doc"; +import { composerTextDirections } from "./composer-text-direction"; + +const schema = getSchemaByResolvedExtensions( + resolveExtensions([ + StarterKit.configure({ + blockquote: false, + bulletList: false, + codeBlock: false, + heading: false, + horizontalRule: false, + listItem: false, + orderedList: false, + code: false, + }), + ComposerCodeExtension, + ComposerCodeBlockExtension, + ...ComposerListExtensions, + ...ComposerBlockExtensions, + ]), +); + +function directions(value: string) { + const doc = ProseMirrorNode.fromJSON( + schema, + buildDocJson(value, (name) => ({ label: name, description: null })), + ); + return composerTextDirections(doc).blocks; +} + +describe("composerTextDirections", () => { + it("does not count inline code toward a line's direction", () => { + expect(directions("`useEffect` و `useLayoutEffect` في `React`")).toEqual(["rtl"]); + }); + + it("gives a list one direction, from all of its items", () => { + expect(directions("- Vitest\n- اختبر الدالة الجديدة\n- وأضف تعليق")).toEqual(["rtl"]); + }); + + it("continues the line above until a line has letters", () => { + expect(directions("اكتب الدالة\n\n42\nfix it")).toEqual(["rtl", "rtl", "rtl", "ltr"]); + }); + + it("keeps a code block left to right inside Arabic text", () => { + expect(directions("اكتب الدالة\n```\n// احسب مجموع القيم هنا\n```\nثم اختبرها")).toEqual([ + "rtl", + "ltr", + "rtl", + ]); + }); +}); diff --git a/apps/web/src/composer-text-direction.ts b/apps/web/src/composer-text-direction.ts new file mode 100644 index 0000000000000..87ac55a86d90a --- /dev/null +++ b/apps/web/src/composer-text-direction.ts @@ -0,0 +1,90 @@ +import { Extension } from "@tiptap/core"; +import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; +import { type EditorState, Plugin, PluginKey } from "@tiptap/pm/state"; +import { Decoration, DecorationSet } from "@tiptap/pm/view"; + +import { textDirection, type TextDirection } from "~/textDirection"; + +interface ComposerTextDirections { + /** One entry per top-level block. */ + readonly blocks: ReadonlyArray; + readonly decorations: DecorationSet; +} + +// Nodes are immutable and blocks are shared between documents, so a keystroke +// only recounts the block it changed, and every reader of one document shares +// one result. +const blockDirections = new WeakMap(); +const docDirections = new WeakMap(); + +function proseText(block: ProseMirrorNode): string { + let text = ""; + block.descendants((node) => { + if (node.isText) { + text += node.marks.some((mark) => mark.type.spec.code) ? " " : node.text; + } else if (node.isBlock || node.isAtom) { + text += " "; + } + }); + return text; +} + +function blockDirection(block: ProseMirrorNode): TextDirection | null { + let direction = blockDirections.get(block); + if (direction === undefined) { + direction = textDirection(proseText(block)) ?? null; + blockDirections.set(block, direction); + } + return direction; +} + +/** + * Lines inside a list or quote follow the block around them, which keeps the + * bullets and the quote rule on the same side as the text. A line without + * letters yet continues the one above, so a new line after Arabic starts on + * the right. Code blocks stay left to right. + */ +export function composerTextDirections(doc: ProseMirrorNode): ComposerTextDirections { + const cached = docDirections.get(doc); + if (cached) return cached; + const blocks: Array = []; + const decorations: Decoration[] = []; + let previous: TextDirection | null = null; + doc.forEach((block, offset) => { + if (block.type.spec.code) { + blocks.push("ltr"); + return; + } + const dir = blockDirection(block) ?? previous; + blocks.push(dir); + if (dir) decorations.push(Decoration.node(offset, offset + block.nodeSize, { dir })); + previous = dir; + }); + const directions = { blocks, decorations: DecorationSet.create(doc, decorations) }; + docDirections.set(doc, directions); + return directions; +} + +export function caretLineDirection(state: EditorState): TextDirection | null { + return composerTextDirections(state.doc).blocks[state.selection.$head.index(0)] ?? null; +} + +export const ComposerTextDirectionExtension = Extension.create({ + name: "composer-text-direction", + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey("composer-text-direction"), + props: { + decorations: (state) => composerTextDirections(state.doc).decorations, + // On Chrome, ProseMirror reads the arrow keys' direction from the + // editor root, so the root follows the line holding the caret. + attributes: (state): Record => { + const dir = caretLineDirection(state); + return dir ? { dir } : {}; + }, + }, + }), + ]; + }, +}); diff --git a/apps/web/src/hooks/useFileMetadata.ts b/apps/web/src/hooks/useFileMetadata.ts index bc5940410ce3b..497cc67d989eb 100644 --- a/apps/web/src/hooks/useFileMetadata.ts +++ b/apps/web/src/hooks/useFileMetadata.ts @@ -1,6 +1,10 @@ import { AuthFilesystemReadScope, type EnvironmentId } from "@t3tools/contracts"; import { resolvePathLinkTarget, splitFilePathPosition } from "@t3tools/shared/fileLinks"; -import { normalizeProjectPathForComparison, isAbsolutePath } from "@t3tools/shared/path"; +import { + isAbsolutePath, + isNetworkPath, + normalizeProjectPathForComparison, +} from "@t3tools/shared/path"; import { useContext } from "react"; import { useEnvironmentScope } from "../state/session"; @@ -21,7 +25,9 @@ export function useFileMetadata( ); const absolute = isAbsolutePath(targetPath) || targetPath.startsWith("~/"); return useEnvironmentQuery( - environmentId && canRead && absolute && targetPath.length <= 512 + // Servers stat what they are asked about, and a network path leaks the + // Windows user's NTLM credentials to its host before any chip is clicked. + environmentId && canRead && absolute && !isNetworkPath(targetPath) && targetPath.length <= 512 ? projectEnvironment.fileMetadata({ environmentId, input: { diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 354a096d6b237..01fa601ab666e 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1848,7 +1848,7 @@ code { custom property, so without this a task-list under a multi-digit ordered list would inherit the outer gutter instead of its own default. */ --list-gutter: 1.25rem; - padding-left: 1.25rem; + padding-inline-start: 1.25rem; list-style-type: disc; } @@ -1859,7 +1859,7 @@ code { nested ol without its own widened marker doesn't inherit the outer one. */ :is(.chat-markdown, .composer-tiptap) ol { --list-gutter: 1.25rem; - padding-left: var(--list-gutter, 1.25rem); + padding-inline-start: var(--list-gutter, 1.25rem); list-style-type: decimal; } @@ -1912,7 +1912,7 @@ code { } .composer-tiptap ul { - padding-left: max(1.25rem, 2ch); + padding-inline-start: max(1.25rem, 2ch); } :is(.chat-markdown, .composer-tiptap) ul ul { @@ -1941,7 +1941,8 @@ code { } .chat-markdown li.task-list-item input[type="checkbox"] { - margin: 0 0.35em 0.15em calc(-1 * var(--list-gutter, 1.25rem)); + margin-block: 0 0.15em; + margin-inline: calc(-1 * var(--list-gutter, 1.25rem)) 0.35em; vertical-align: middle; } @@ -1979,8 +1980,8 @@ code { } :is(.chat-markdown, .composer-tiptap) blockquote { - border-left: 2px solid var(--contrast-border); - padding-left: 0.8rem; + border-inline-start: 2px solid var(--contrast-border); + padding-inline-start: 0.8rem; color: var(--contrast-muted-foreground); } @@ -2132,7 +2133,7 @@ code { .chat-markdown th, .chat-markdown td { padding: 0.45rem 0.75rem; - text-align: left; + text-align: start; } .chat-markdown thead th { @@ -2401,7 +2402,7 @@ code { } .composer-tiptap ul[data-type="taskList"] ul[data-type="taskList"] { - padding-left: 1.375rem; + padding-inline-start: 1.375rem; } .composer-tiptap li[data-checked] { diff --git a/apps/web/src/lib/addProjectToMachine.ts b/apps/web/src/lib/addProjectToMachine.ts index 367420c45e0f5..bccfc9d84910e 100644 --- a/apps/web/src/lib/addProjectToMachine.ts +++ b/apps/web/src/lib/addProjectToMachine.ts @@ -17,47 +17,9 @@ import { environmentProjectClonesAtom, silenceProjectCloneToasts } from "../stat import { projectEnvironment } from "../state/projects"; import { sourceControlEnvironment } from "../state/sourceControl"; -export type CloneProtocol = "ssh" | "https"; - /** Matches the server's read cap: a larger file would arrive truncated. */ export const COPYABLE_FILE_MAX_BYTES = 1024 * 1024; -const SCP_LIKE_URL = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.+)$/; -const SSH_URL = /^ssh:\/\/(?:[^@/\s]+@)?([^:/\s]+)(:\d+)?\/(.+)$/; -const HTTPS_URL = /^https?:\/\/(?:[^@/\s]+@)?([^:/\s]+)(:\d+)?\/(.+)$/; - -function parseCloneUrl( - url: string, -): { protocol: CloneProtocol; host: string; path: string; customPort: boolean } | null { - const trimmed = url.trim(); - const ssh = SSH_URL.exec(trimmed); - if (ssh) return { protocol: "ssh", host: ssh[1]!, path: ssh[3]!, customPort: !!ssh[2] }; - const https = HTTPS_URL.exec(trimmed); - if (https) return { protocol: "https", host: https[1]!, path: https[3]!, customPort: !!https[2] }; - const scp = SCP_LIKE_URL.exec(trimmed); - if (scp) return { protocol: "ssh", host: scp[1]!, path: scp[2]!, customPort: false }; - return null; -} - -export function cloneUrlProtocol(url: string): CloneProtocol | null { - return parseCloneUrl(url)?.protocol ?? null; -} - -/** - * Rewrites a clone URL to the other protocol, because the target machine may - * only have SSH keys or only an HTTPS credential helper. Returns null when the - * URL cannot be rewritten safely, such as a custom port. - */ -export function cloneUrlForProtocol(url: string, protocol: CloneProtocol): string | null { - const parsed = parseCloneUrl(url); - if (!parsed) return null; - if (parsed.protocol === protocol) return url.trim(); - if (parsed.customPort) return null; - return protocol === "ssh" - ? `git@${parsed.host}:${parsed.path}` - : `https://${parsed.host}/${parsed.path}`; -} - const DEFAULT_COPIED_FILE_NAME = /^(?:\.env(?:\..+)?|\.envrc|\.dev\.vars)$/; /** Env files are the ignored files people almost always want on a new machine. */ diff --git a/apps/web/src/lib/contextWindow.ts b/apps/web/src/lib/contextWindow.ts index 23ed466e3ba19..32c3bb8de30f1 100644 --- a/apps/web/src/lib/contextWindow.ts +++ b/apps/web/src/lib/contextWindow.ts @@ -1,9 +1,8 @@ +import type { ThreadStructure, ThreadStructureTurnItem } from "@t3tools/client-runtime/state/shell"; import type { OrchestrationV2ProviderTurn, OrchestrationV2ProviderTurnTokenUsage, OrchestrationV2ProviderThread, - OrchestrationV2ThreadProjection, - OrchestrationV2TurnItem, ThreadTokenUsageSnapshot, } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; @@ -29,7 +28,7 @@ export type ContextWindowSnapshot = NullableContextWindowUsage & { by provider thread, not time, so pick by timestamp. */ export function latestProviderTurnTokenUsage( turns: ReadonlyArray>, - projection?: Pick | null, + projection?: Pick | null, providerThread?: Pick | null, ): OrchestrationV2ProviderTurnTokenUsage | null { let latest: OrchestrationV2ProviderTurnTokenUsage | null = null; @@ -58,7 +57,7 @@ export function latestProviderTurnTokenUsage( export function deriveLatestContextWindowSnapshot( entries: ReadonlyArray<{ - readonly item: OrchestrationV2TurnItem; + readonly item: ThreadStructureTurnItem; }>, liveUsage?: OrchestrationV2ProviderTurnTokenUsage | null, providerThread?: Pick | null, diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 0d6956fa37893..b504606b4d5e1 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -1,3 +1,4 @@ +import type { ThreadStructure, ThreadStructureRuntime } from "@t3tools/client-runtime/state/shell"; import { resolveThreadWorkingStartedAt } from "@t3tools/client-runtime/state/models"; import { type AssetResource, @@ -251,7 +252,7 @@ export function derivePendingUserInputs( } export function deriveActivePlanState( - projection: OrchestrationV2ThreadProjection | null, + projection: ThreadStructure | null, latestRunId: RunId | undefined, ): ActivePlanState | null { if (projection === null) return null; @@ -273,7 +274,7 @@ export function deriveActivePlanState( ); if (plan === null || plan.steps.length === 0) return null; return { - createdAt: planItemTime(projection, plan.id), + createdAt: planItemTime(projection, plan), runId: plan.runId, explanation: plan.explanation ?? null, steps: plan.steps.map(({ text, status, durationMs }) => ({ @@ -286,7 +287,7 @@ export function deriveActivePlanState( // Claude's unfinished tasks belong to the provider session, not just the turn. export function selectComposerTasksPlan( - projection: OrchestrationV2ThreadProjection | null, + projection: ThreadStructure | null, plan: ActivePlanState | null, activeRunId: RunId | null, activeRunSettled: boolean, @@ -315,20 +316,30 @@ export function selectComposerTasksPlan( return latestStartedRun?.providerThreadId === planProviderThreadId ? plan : null; } -function planItemTime(projection: OrchestrationV2ThreadProjection, planId: PlanId): string { +function planItemTime( + projection: ThreadStructure, + plan: Pick, +): string { const item = projection.turnItems.findLast( (candidate) => (candidate.type === "proposed_plan" || candidate.type === "todo_list") && - candidate.planId === planId, + candidate.planId === plan.id, + ); + if (item?.type === "proposed_plan" || item?.type === "todo_list") { + return DateTime.formatIso(item.updatedAt); + } + // Without a plan item, the plan's run dates it; the thread's own timestamps move while a reply streams. + const run = projection.runs.find((candidate) => candidate.id === plan.runId); + return DateTime.formatIso( + run?.completedAt ?? run?.startedAt ?? run?.requestedAt ?? projection.thread.createdAt, ); - return DateTime.formatIso(item?.updatedAt ?? projection.updatedAt); } function toLatestProposedPlanState( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, plan: Extract, ): LatestProposedPlanState { - const updatedAt = planItemTime(projection, plan.id); + const updatedAt = planItemTime(projection, plan); return { id: plan.id, createdAt: updatedAt, @@ -340,7 +351,7 @@ function toLatestProposedPlanState( } export function findLatestProposedPlan( - projection: OrchestrationV2ThreadProjection | null, + projection: ThreadStructure | null, latestRunId: RunId | string | null | undefined, ): LatestProposedPlanState | null { if (projection === null) return null; @@ -349,7 +360,7 @@ export function findLatestProposedPlan( const plan = [...(candidates.length > 0 ? candidates : plans)] .toSorted( (left, right) => - planItemTime(projection, left.id).localeCompare(planItemTime(projection, right.id)) || + planItemTime(projection, left).localeCompare(planItemTime(projection, right)) || left.id.localeCompare(right.id), ) .at(-1); @@ -1122,7 +1133,7 @@ export function findRevertCheckpoint( ); } -export function derivePhase(runtime: ThreadRuntimeSummary | null): SessionPhase { +export function derivePhase(runtime: ThreadStructureRuntime | null): SessionPhase { if (runtime === null) return "disconnected"; if ( runtime.status === "preparing" || @@ -1140,7 +1151,7 @@ export function derivePhase(runtime: ThreadRuntimeSummary | null): SessionPhase */ export function deriveCanInterruptRunningThread( hasActiveThread: boolean, - runtime: ThreadRuntimeSummary | null, + runtime: ThreadStructureRuntime | null, ): boolean { return ( hasActiveThread && diff --git a/apps/web/src/state/entities.ts b/apps/web/src/state/entities.ts index 012bbb82ce252..e88422aa54e1a 100644 --- a/apps/web/src/state/entities.ts +++ b/apps/web/src/state/entities.ts @@ -1,8 +1,11 @@ +import type * as DateTime from "effect/DateTime"; import { useAtomValue } from "@effect/atom-react"; import type { EnvironmentProject, EnvironmentThread, EnvironmentThreadShell, + EnvironmentThreadStructure, + ThreadStructureVisibleTurnItem, } from "@t3tools/client-runtime/state/shell"; import { EMPTY_THREAD_HISTORY_META, @@ -62,6 +65,12 @@ const EMPTY_VISIBLE_TURN_ITEMS_ATOM = Atom.make(EMPTY_VISIBLE_TURN_ITEMS).pipe( const EMPTY_CHECKPOINT_SUMMARIES_ATOM = Atom.make>( Object.freeze([]), ).pipe(Atom.withLabel("web-thread-checkpoint-summaries:empty")); +const EMPTY_LAST_VISITED_AT_ATOM = Atom.make(null).pipe( + Atom.withLabel("web-thread-last-visited:empty"), +); +const EMPTY_UPDATED_AT_ATOM = Atom.make(null).pipe( + Atom.withLabel("web-thread-updated-at:empty"), +); const EMPTY_THREAD_HISTORY_ATOM = Atom.make(EMPTY_THREAD_HISTORY_META).pipe( Atom.withLabel("web-thread-history:empty"), ); @@ -156,6 +165,29 @@ export function useThreadProjection(ref: ScopedThreadRef | null): EnvironmentThr ); } +/** The thread's structure, held through streamed assistant and reasoning text; see ThreadStructure. */ +export function useQuietThreadProjection( + ref: ScopedThreadRef | null, +): EnvironmentThreadStructure | null { + return useAtomValue( + ref === null ? EMPTY_THREAD_PROJECTION_ATOM : environmentThreadDetails.quietThreadAtom(ref), + ); +} + +/** The detail's live visit mark, which the thread structure leaves out. */ +export function useThreadLastVisitedAt(ref: ScopedThreadRef | null): DateTime.Utc | null { + return useAtomValue( + ref === null ? EMPTY_LAST_VISITED_AT_ATOM : environmentThreadDetails.lastVisitedAtAtom(ref), + ); +} + +/** The detail's live `updatedAt`, which the thread structure leaves out. */ +export function useThreadUpdatedAt(ref: ScopedThreadRef | null): DateTime.Utc | null { + return useAtomValue( + ref === null ? EMPTY_UPDATED_AT_ATOM : environmentThreadDetails.updatedAtAtom(ref), + ); +} + export function useThreadStatus(ref: ScopedThreadRef | null): EnvironmentThreadStatus { return useAtomValue( ref === null ? EMPTY_THREAD_STATUS_ATOM : environmentThreadDetails.statusAtom(ref), @@ -188,6 +220,17 @@ export function useThreadVisibleTurnItems( ); } +/** Visible turn items without their streamed text; see ThreadStructure. */ +export function useQuietThreadVisibleTurnItems( + ref: ScopedThreadRef | null, +): ReadonlyArray { + return useAtomValue( + ref === null + ? EMPTY_VISIBLE_TURN_ITEMS_ATOM + : environmentThreadDetails.quietVisibleTurnItemsAtom(ref), + ); +} + export function useThreadCheckpointSummaries( ref: ScopedThreadRef | null, ): ReadonlyArray { diff --git a/apps/web/src/textDirection.test.ts b/apps/web/src/textDirection.test.ts new file mode 100644 index 0000000000000..a104133799c52 --- /dev/null +++ b/apps/web/src/textDirection.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { textDirection } from "./textDirection"; + +describe("textDirection", () => { + it.each([ + ["Arabic", "اكتب دالة تجمع الأرقام", "rtl"], + ["Hebrew", "תקן את הבאג", "rtl"], + ["English", "fix the failing test", "ltr"], + [ + "Arabic that opens with an English term", + "StrictMode: يعيد الـ render مرتين في التطوير", + "rtl", + ], + ["English quoting one Arabic word", "translate the label مرحبا for the login screen", "ltr"], + ["a tie, by its first word", "React مكتبة", "ltr"], + ["a tie that opens in Arabic", "مكتبة React", "rtl"], + ["Arabic around numbers and punctuation", "عدّل السطر 42، ثم شغّل الاختبارات (3)", "rtl"], + ])("reads %s", (_, text, direction) => { + expect(textDirection(text)).toBe(direction); + }); + + it("leaves text without letters to its surroundings", () => { + expect(textDirection("42 — 7 ✓")).toBeUndefined(); + }); +}); diff --git a/apps/web/src/textDirection.ts b/apps/web/src/textDirection.ts new file mode 100644 index 0000000000000..0f5d863a938ec --- /dev/null +++ b/apps/web/src/textDirection.ts @@ -0,0 +1,31 @@ +export type TextDirection = "ltr" | "rtl"; + +// Hebrew through Arabic Extended-A, their presentation forms, and the +// supplementary right-to-left blocks. +const RTL_LETTER = /[֐-ࣿיִ-﷿ﹰ-\u{10800}-\u{10FFF}\u{1E800}-\u{1EFFF}]/u; +const LETTER = /\p{L}/u; + +/** + * The base direction for a block of prose, or undefined when it has no letters. + * + * `dir="auto"` follows the first letter, which lays out an Arabic line that + * opens with an English term (`StrictMode: يعيد الـ render مرتين`) left to + * right, and developers write that way constantly. Counting words keeps such a + * line right to left, while English with one quoted Arabic word stays left to + * right. A tie goes to the first word, as `dir="auto"` would. + */ +export function textDirection(text: string): TextDirection | undefined { + let rtlWords = 0; + let ltrWords = 0; + let first: TextDirection | undefined; + for (const word of text.split(/\s+/)) { + const letter = LETTER.exec(word)?.[0]; + if (!letter) continue; + const direction = RTL_LETTER.test(letter) ? "rtl" : "ltr"; + first ??= direction; + if (direction === "rtl") rtlWords += 1; + else ltrWords += 1; + } + if (rtlWords === ltrWords) return first; + return rtlWords > ltrWords ? "rtl" : "ltr"; +} diff --git a/apps/web/src/themePalette.ts b/apps/web/src/themePalette.ts index 3e9582ce22a1e..5995f6aef1d6a 100644 --- a/apps/web/src/themePalette.ts +++ b/apps/web/src/themePalette.ts @@ -558,6 +558,25 @@ function solveOklchLightness( return { ...base, L: direction === "lighter" ? high : low }; } +/** + * Moves only the lightness, so a color that is too dim on `surface` keeps its + * hue instead of collapsing to black or white. + */ +export function readableThemeColorOn(color: string, surface: string, minContrast: number): string { + const parsedColor = parseThemeColor(color); + const parsedSurface = parseThemeColor(surface); + if (!parsedColor || !parsedSurface) return color; + const surfaceRgb = themeOklchToRgb(parsedSurface.color); + if (themeContrastRatio(themeOklchToRgb(parsedColor.color), surfaceRgb) >= minContrast) { + return color; + } + const direction = themeRelativeLuminance(surfaceRgb) < 0.179 ? "lighter" : "darker"; + const solved = solveOklchLightness(parsedColor.color, surfaceRgb, minContrast, direction); + // Return the 8-bit color the solver measured: the unrounded OKLCH can round + // to a neighbor just under the target once it is stored and read back. + return themeRgbToHexColor(themeOklchToRgb(solved)); +} + const STANDARD_STATUS_COLORS = { light: { error: "#fb2c36", diff --git a/apps/web/src/vscodeThemeImport.test.ts b/apps/web/src/vscodeThemeImport.test.ts index bb51a3e9edbb5..e4774c5f2b339 100644 --- a/apps/web/src/vscodeThemeImport.test.ts +++ b/apps/web/src/vscodeThemeImport.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from "vite-plus/test"; -import { getThemeColorsForMode, themeColorToHex, THEME_FILE_VERSION } from "./themePalette"; +import { + getThemeColorsForMode, + themeColorToHex, + THEME_FILE_VERSION, + toCanonicalThemeColor, +} from "./themePalette"; import { isVsCodeThemeFile, pairVsCodeThemes, @@ -30,6 +35,11 @@ function contrastRatio(first: string, second: string): number { return (lighter + 0.05) / (darker + 0.05); } +function oklchParts(value: string): { chroma: number; hue: number } { + const [, chroma, hue] = /^oklch\(\S+ (\S+) (\S+)\)$/.exec(value)!; + return { chroma: Number(chroma), hue: Number(hue) }; +} + const VSCODE_DARK = { name: "pierre-dark-soft", type: "dark", @@ -293,6 +303,45 @@ describe("VS Code theme import", () => { expect(hc.appearance).toBe("light"); }); + it.each([ + ["dark", "#282828"], + ["light", "#ffffff"], + ] as const)( + "keeps a dim status color's hue on a %s canvas instead of replacing it", + (type, canvas) => { + const theme = parseVsCodeThemeFile({ + name: "Gruvbox", + type, + colors: { + "editor.background": canvas, + "editorError.foreground": "#fb4934", + "editorWarning.foreground": "#b57614", + }, + }); + for (const [role, source] of [ + ["error", "#fb4934"], + ["warning", "#b57614"], + ] as const) { + const imported = oklchParts(theme.colors[role]); + expect(contrastRatio(theme.colors[role], theme.colors.canvas)).toBeGreaterThanOrEqual(4.5); + expect(imported.chroma).toBeGreaterThan(0.1); + // 8-bit sRGB can only approximate the source hue at the new lightness. + expect( + Math.abs(imported.hue - oklchParts(toCanonicalThemeColor(source)!).hue), + ).toBeLessThan(2); + } + }, + ); + + it("keeps the derived status color when the file hides its own", () => { + const theme = parseVsCodeThemeFile({ + name: "Hidden squiggles", + type: "dark", + colors: { "editor.background": "#101010", "editorError.foreground": "#ff000000" }, + }); + expect(oklchParts(theme.colors.error).chroma).toBeGreaterThan(0.1); + }); + it("stays readable when the file replaces a surface but not its foreground", () => { const theme = parseVsCodeThemeFile({ name: "Split", diff --git a/apps/web/src/vscodeThemeImport.ts b/apps/web/src/vscodeThemeImport.ts index f654679a5ad5e..7ef3d77835ef5 100644 --- a/apps/web/src/vscodeThemeImport.ts +++ b/apps/web/src/vscodeThemeImport.ts @@ -4,6 +4,7 @@ import { getThemeModes, isReservedThemeId, parseThemeFile, + readableThemeColorOn, themeColorToHex, themeIdFromName, THEME_FILE_VERSION, @@ -251,6 +252,15 @@ export function parseVsCodeThemeFile(value: unknown): ThemeDefinition { if (isReadable(fallback)) return fallback; return relativeLuminance(surfaceRgb) < 0.179 ? "#ffffff" : "#000000"; }; + // Status colors also fill controls such as destructive buttons, which carry + // white text, so a dim one is lightened along its hue rather than replaced + // by readableOn's black or white. One hidden in the canvas has no hue to + // keep, so the derived color stands in. + const readableStatusColor = (fallback: string, ...keys: ReadonlyArray): string => { + const specified = solidOver(canvas, ...keys); + const base = specified && standsApart(specified, canvasHex) ? specified : fallback; + return readableThemeColorOn(base, canvasHex, 4.5); + }; const surfaceRaisedHex = raisedSurfaceCandidateHex ?? derived.surfaceRaised; // The checked switch track maps to messageAction, so resolve it before @@ -302,8 +312,8 @@ export function parseVsCodeThemeFile(value: unknown): ThemeDefinition { solidOver(canvas, "panel.border", "editorGroup.border", "contrastBorder") ?? derived.border, input: inputHex, placeholder: readableOn(surfaceRaisedHex, derived.placeholder, "input.placeholderForeground"), - error: readableOn(canvasHex, derived.error, "editorError.foreground", "errorForeground"), - warning: readableOn(canvasHex, derived.warning, "editorWarning.foreground"), + error: readableStatusColor(derived.error, "editorError.foreground", "errorForeground"), + warning: readableStatusColor(derived.warning, "editorWarning.foreground"), accentSurface: solidOver(canvas, "list.activeSelectionBackground", "list.hoverBackground") ?? derived.accentSurface, diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index f376d7b6eb27a..d039a5428c572 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -190,9 +190,9 @@ export default defineConfig(() => { return { run: { tasks: { - // tsc-rs rewrites the tsbuildinfo it reads, which keeps a task out of the cache. + // tsc (tsc-rs) rewrites the tsbuildinfo it reads, which keeps a task out of the cache. typecheck: { - command: "tsc-rs --noEmit", + command: "tsc --noEmit", cache: { input: [{ auto: true }, "!*.tsbuildinfo"] }, }, }, diff --git a/docs/operations/development.md b/docs/operations/development.md index 8f4feef0d760f..d3c3226d9b048 100644 --- a/docs/operations/development.md +++ b/docs/operations/development.md @@ -129,11 +129,9 @@ In `apps/web`, a test that renders React or touches the DOM is named `*.browser. and runs in headless Chromium with the React Compiler on, like the app. There is no jsdom; other `*.test.ts` files run in Node. -TypeScript workspaces typecheck with tsc-rs, which has Effect diagnostics built in. -Keep `typescript`, `@effect/tsgo`, and the `effect-tsgo patch` prepare step: editors -still use them for Effect quick fixes and refactors. Keep `@effect/tsgo` on the -effect-tsgo version that tsc-rs ports (see the tsc-rs README), or the two disagree -on rule names. +TypeScript workspaces typecheck with `tsc`. The `typescript` catalog entry is +`@tsc-rs/typescript`, the tsc-rs drop-in, with Effect diagnostics, quick fixes, and +refactors built in. VS Code uses it through `js/ts.tsdk.path` in `.vscode/settings.json`. Marketing keeps `astro check` for Astro files. Use `vp run lint:mobile` for native mobile changes. CI owns the full suite; see diff --git a/docs/operations/mobile-screens-fork.md b/docs/operations/mobile-screens-fork.md index cd21aa551370b..d7d0313a5929c 100644 --- a/docs/operations/mobile-screens-fork.md +++ b/docs/operations/mobile-screens-fork.md @@ -1,7 +1,7 @@ # Building the mobile Screens dependency The mobile v5 stack uses a committed tarball from the -[T3 Screens fork](https://github.com/pingdotgg/react-native-screens/tree/t3-v5.0.0-t3.30). +[T3 Screens fork](https://github.com/pingdotgg/react-native-screens/tree/t3-v5.0.0-t3.36). The fork owns the integration changes; the app does not patch this package. Each archive includes compiled JavaScript, declarations, native source and `t3-fork.json` recording its version, source commit and upstream base. diff --git a/docs/operations/release.md b/docs/operations/release.md index c6725efdcdad4..a55643e4ef349 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -44,7 +44,7 @@ the version. - Includes Electron auto-update metadata (for example `latest*.yml`, `nightly*.yml`, and `*.blockmap`) in release assets. - Builds a self-contained CLI archive per platform (`t3---.tar.gz`, `.zip` on Windows) in the same job as that target's desktop artifact and attaches them to the GitHub Release with a `SHA256SUMS` file, on every channel, for five targets: macOS arm64, Linux x64 and arm64, Windows x64 and arm64. Every archive is smoke-tested on hardware of its own architecture, and every one but the Windows archives is also built and signed there. There is no macOS x64 archive: Node single-executables are unsupported on x64 macOS (the SEA docs list macOS as arm64 only) and the binary segfaults on start; the x64 desktop app is Electron and unaffected. - The archive holds the server as a Node single-executable (`scripts/build-cli-archive.ts`), so unpacking it needs neither Node, npm, nor a compiler. It is the only form in which T3 Code manages a runtime: the desktop's SSH environments, the boot service, `t3 update`, and the install scripts all download and verify this archive against `SHA256SUMS`. The `t3` npm package exists for people who run `npx t3` or `npm install -g t3` themselves, and it downloads this same archive too; nothing in the product installs from npm. The `curl | sh` installers are `scripts/install.sh` and `scripts/install.ps1`; the marketing site copies them into its `public/` at build time (`apps/marketing/scripts/stage-install-scripts.mjs`) and serves them at `t3.codes/install.sh` and `/install.ps1`. - - The executable is built with a Node that supports `--build-sea` (`VP_NODE_VERSION=26.8.2`, kept in step with `SEA_NODE_VERSION` in `apps/server/vite.config.ts`), while the repo stays on `engines.node`. + - The root `package.json` pins `engines.node` to the exact Node version used by development, CI and standalone executables. Keep it exact: cross-target builds download that version, and native executable builds reject a different host Node version. Runtime changes need a new T3 release version because installed runtimes are cached by that version. - macOS archives are signed with the Developer ID certificate and notarized when the Apple secrets are present (ad hoc otherwise, which still runs from `curl`/`tar` installs). Windows executables use the same Azure Trusted Signing setup as the installer. Every native addon in the macOS archive is signed too, since the hardened runtime refuses unsigned libraries. - Each archive is extracted and executed (`scripts/smoke-cli-archive.ts`) on its build runner, or on the `verify_runner` for Windows. - After the GitHub Release is published, publishes the `t3` npm package with OIDC trusted publishing from the same workflow file. It is only a launcher (`scripts/npm-launcher/t3.mjs`): on first run it downloads its exact version's CLI archive from the GitHub Release, verifies it against `SHA256SUMS`, and unpacks it into `/runtime/versions/`, the layout the install scripts and `t3 update` share. The executables are not on npm. They used to be, as `@t3code/t3--` optional dependencies, but npm's publish-time scanning held those 80-250 MB packages back for up to an hour after the launcher was live, and every install in that window got a launcher with no executable. Those packages stay on npm at their old versions and get no new ones. Because the launcher reads from the release, `publish_cli` runs after `release`, never before. diff --git a/docs/user/source-control.md b/docs/user/source-control.md index bd7715c044a3a..cb323ea4c3bf2 100644 --- a/docs/user/source-control.md +++ b/docs/user/source-control.md @@ -116,7 +116,7 @@ kept in the server's secret store, applies right away, and is used before `CAFE_ sent to `git.cafe`; `CAFE_TOKEN` follows `CAFE_HOST` if you set it. Repositories on `staging.git.cafe` need their own login with `--host https://staging.git.cafe/api`. -Line comments, reviewer requests, and labels are not available for GitCafe pull requests yet. +Line comments are not available for GitCafe pull requests yet. Merging a stack lands it through GitCafe, and updating a branch restacks the layers above it. ## Start, clone, or publish a project @@ -134,7 +134,9 @@ Use **Add Project** in the command palette (`Cmd/Ctrl+K`) to clone a repository. provider or paste a Git URL, then choose where to save it. The project opens right away while the clone runs in the background: you can write your first prompt, and sending waits until the files are in place. A toast tracks progress and lets you cancel; if the clone fails, retry it from the -toast or from the banner above the composer. +toast or from the banner above the composer. When the host refuses the machine's SSH key, T3 Code +clones over HTTPS with Git's credentials for that host instead, and the project's remote uses the +HTTPS URL. For a local Git repository without a remote, **Publish Repository** creates a hosted repository, adds it as `origin`, and pushes your commits. If there are no commits yet, it creates the remote; diff --git a/docs/user/usage.md b/docs/user/usage.md index cab53870bb14c..a012e73d94b89 100644 --- a/docs/user/usage.md +++ b/docs/user/usage.md @@ -79,7 +79,7 @@ the dialog. ## Track subscription limits **Usage → Limits** pools every subscription account it can see per provider, so with several Codex -or Claude accounts across your environments and hubs you read one number per window rather than a +or Claude accounts across your enabled environments and hubs you read one number per window rather than a list. Each window card shows how much of the pool is left and a bar with one segment per account, kept in the same column across windows. Accounts are ordered by their next 5-hour refill, soonest first. For an account with no weekly quota left, this is when its session becomes usable again. diff --git a/docs/user/welcome-wizard.md b/docs/user/welcome-wizard.md index 5747d72606edb..d4604fa7e6668 100644 --- a/docs/user/welcome-wizard.md +++ b/docs/user/welcome-wizard.md @@ -83,10 +83,12 @@ It omits tool activity and attachments. For Codex, it omits generated setup context only when a canonical user event and a valid shared turn ID identify the same user turn. Ambiguous legacy or response-only context stays in the imported conversation so T3 Code does not remove user text. It reads one conversation at -a time and skips files larger than 16 MiB. It ignores malformed records and skips +a time and skips files larger than 4 GiB. It also skips a conversation when the +message text and metadata it keeps need more than 32 MiB of memory. Tool output +does not count toward that limit. It ignores malformed records and skips unreadable or unparseable conversations. -Each import attempt reads up to 100 conversation files and 64 MiB per project, +Each import attempt reads up to 100 conversation files and 4 GiB per project, with up to 100,000 input records. Run import again to continue a large batch. Completed conversations are not imported again. You can continue without the remaining history. diff --git a/infra/relay/package.json b/infra/relay/package.json index dd2265aafb0d5..6ff488ec80e2e 100644 --- a/infra/relay/package.json +++ b/infra/relay/package.json @@ -9,7 +9,7 @@ "tunnels:census": "node scripts/tunnel-census.ts", "destroy": "alchemy destroy", "test": "vp test run --config ../../vite.config.ts --dir .", - "typecheck": "tsc-rs --noEmit" + "typecheck": "tsc --noEmit" }, "dependencies": { "@clerk/backend": "catalog:", diff --git a/oxlint-plugin-t3code/package.json b/oxlint-plugin-t3code/package.json index 64c24c42bc431..35f13bbf38288 100644 --- a/oxlint-plugin-t3code/package.json +++ b/oxlint-plugin-t3code/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../vite.config.ts --dir ." }, "dependencies": { diff --git a/package.json b/package.json index be1f45d4f20cc..bce3d1c34fcb2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "prepare": "effect-tsgo patch && vp config --no-agent", + "prepare": "vp config --no-agent", "dev": "node scripts/dev-runner.ts dev", "dev:share": "node scripts/dev-runner.ts dev --share", "dev:server": "node scripts/dev-runner.ts dev:server", @@ -53,16 +53,14 @@ }, "devDependencies": { "@coderabbitai/config": "1.1.0", - "@effect/tsgo": "catalog:", "@shadcn/lint": "0.1.5", "@types/node": "catalog:", "knip": "6.34.0", - "tsc-rs": "0.7.0", "typescript": "catalog:", "vite-plus": "catalog:" }, "engines": { - "node": "^24.13.1" + "node": "26.12.1" }, "packageManager": "pnpm@12.14.0" } diff --git a/packages/client-runtime/package.json b/packages/client-runtime/package.json index dc636909ef654..4c04254c257ed 100644 --- a/packages/client-runtime/package.json +++ b/packages/client-runtime/package.json @@ -449,7 +449,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run" }, "dependencies": { diff --git a/packages/client-runtime/src/handoff.ts b/packages/client-runtime/src/handoff.ts index 1ac4db202bb7f..27c0f38c17600 100644 --- a/packages/client-runtime/src/handoff.ts +++ b/packages/client-runtime/src/handoff.ts @@ -1,12 +1,13 @@ import type { ModelSelection, OrchestrationV2Run, - OrchestrationV2ThreadProjection, OrchestrationV2TurnItem, ProviderInstanceId, ServerProvider, } from "@t3tools/contracts"; +import type { ThreadStructure } from "./state/models.ts"; + export type HandoffTimelineRun = Pick< OrchestrationV2Run, "id" | "ordinal" | "providerInstanceId" | "modelSelection" @@ -15,7 +16,7 @@ export type HandoffTimelineRun = Pick< export type HandoffEndpoints = ReturnType; export function resolvePendingHandoff( - projection: OrchestrationV2ThreadProjection | null | undefined, + projection: ThreadStructure | null | undefined, selection: ModelSelection | null | undefined, providers: ReadonlyArray, ): HandoffEndpoints | null { diff --git a/packages/client-runtime/src/markdownImages.test.ts b/packages/client-runtime/src/markdownImages.test.ts index 039eb75345cfa..b34c8bce39d32 100644 --- a/packages/client-runtime/src/markdownImages.test.ts +++ b/packages/client-runtime/src/markdownImages.test.ts @@ -11,7 +11,7 @@ describe("classifyMarkdownImageSource", () => { "C:\\Users\\dara\\project\\images\\result.png", ], ["file:///workspace/project/image%20one.png", null, "/workspace/project/image one.png"], - ["file://server/share/image.png", null, "\\\\server\\share\\image.png"], + ["file://server/share/image.png", "\\\\server\\share", "\\\\server\\share\\image.png"], ])("maps %s to a workspace file", (source, workspaceRoot, path) => { expect(classifyMarkdownImageSource(source, workspaceRoot)).toEqual({ _tag: "WorkspaceFile", @@ -25,4 +25,17 @@ describe("classifyMarkdownImageSource", () => { expect(classifyMarkdownImageSource(source)).toEqual({ _tag: "Blocked" }); }, ); + + // Loading these makes Windows send the user's NTLM credentials to the named host. + it.each([ + "file://attacker.test/share/cat.jpg", + "\\\\attacker.test\\share\\cat.jpg", + "/\\attacker.test\\share\\cat.jpg", + "file:/%5Cattacker.test%5Cshare%5Ccat.jpg", + "\\\\server\\other\\cat.jpg", + ])("blocks network path %s outside the project", (source) => { + expect(classifyMarkdownImageSource(source, "\\\\server\\share\\project")).toEqual({ + _tag: "Blocked", + }); + }); }); diff --git a/packages/client-runtime/src/markdownImages.ts b/packages/client-runtime/src/markdownImages.ts index 55e597f4d3c2a..924220ec27ce4 100644 --- a/packages/client-runtime/src/markdownImages.ts +++ b/packages/client-runtime/src/markdownImages.ts @@ -1,4 +1,9 @@ -import { isWindowsAbsolutePath, stripSlashPrefixedWindowsDrive } from "@t3tools/shared/path"; +import { + isNetworkPath, + isWindowsAbsolutePath, + stripSlashPrefixedWindowsDrive, + workspaceRelativeFilePath, +} from "@t3tools/shared/path"; import { normalizeMarkdownLinkDestination, @@ -26,6 +31,16 @@ function joinWorkspacePath(workspaceRoot: string, relativePath: string): string return `${root}${separator}${path}`; } +/** Rendering is not a click, so a network path loads only from the project's own share. */ +function hostFileSource( + path: string, + workspaceRoot: string | null | undefined, +): MarkdownImageSource { + return isNetworkPath(path) && workspaceRelativeFilePath(path, workspaceRoot) === null + ? { _tag: "Blocked" } + : { _tag: "WorkspaceFile", path }; +} + /** * Filesystem paths belong to the environment host and must never reach a * browser or native image component without first becoming a signed asset URL. @@ -48,10 +63,10 @@ export function classifyMarkdownImageSource( const target = parseFileUrlHref(source); return target === null ? { _tag: "Blocked" } - : { - _tag: "WorkspaceFile", - path: stripSlashPrefixedWindowsDrive(safeDecodeURIComponent(target.path)), - }; + : hostFileSource( + stripSlashPrefixedWindowsDrive(safeDecodeURIComponent(target.path)), + workspaceRoot, + ); } const path = stripSlashPrefixedWindowsDrive( @@ -59,7 +74,7 @@ export function classifyMarkdownImageSource( ); if (path.length === 0) return { _tag: "Blocked" }; if (path.startsWith("/") || isWindowsAbsolutePath(path)) { - return { _tag: "WorkspaceFile", path }; + return hostFileSource(path, workspaceRoot); } if (URI_SCHEME_PATTERN.test(path) || path.startsWith("~/") || path.startsWith("~\\")) { return { _tag: "Blocked" }; diff --git a/packages/client-runtime/src/mediaSource.test.ts b/packages/client-runtime/src/mediaSource.test.ts index 612008c5be569..1b46b463a83d7 100644 --- a/packages/client-runtime/src/mediaSource.test.ts +++ b/packages/client-runtime/src/mediaSource.test.ts @@ -35,7 +35,6 @@ describe("resolveMediaSource", () => { it.each([ ["/tmp/frame%23one.png:12", "/tmp/frame#one.png"], ["/tmp/frame%2523one.png:12", "/tmp/frame%23one.png"], - ["file://server/share/frame.png", "\\\\server\\share\\frame.png"], ])("resolves %s through the environment", (href, path) => { expect(resolveMediaSource(href, { threadId, workspaceRoot: "/repo" })).toEqual({ kind: "image", diff --git a/packages/client-runtime/src/state/assets.ts b/packages/client-runtime/src/state/assets.ts index 22052e6ade824..36a789b3089ad 100644 --- a/packages/client-runtime/src/state/assets.ts +++ b/packages/client-runtime/src/state/assets.ts @@ -80,6 +80,7 @@ export type AssetUrlState = readonly expiresAt: number; readonly sourcePath?: string; readonly imageDimensions?: AssetImageDimensions; + readonly fileRevision?: string; }; export const MISSING_FILE_ACCESS_MESSAGE = @@ -124,6 +125,7 @@ export function assetUrlStateFromResult( ...(result.value.imageDimensions !== undefined ? { imageDimensions: result.value.imageDimensions } : {}), + ...(result.value.fileRevision !== undefined ? { fileRevision: result.value.fileRevision } : {}), }; } diff --git a/packages/client-runtime/src/state/models.ts b/packages/client-runtime/src/state/models.ts index ba78e2304d75b..537f1455a4129 100644 --- a/packages/client-runtime/src/state/models.ts +++ b/packages/client-runtime/src/state/models.ts @@ -34,6 +34,49 @@ export interface EnvironmentThread { readonly projection: OrchestrationV2ThreadProjection; } +type ProjectionTurnItem = OrchestrationV2ThreadProjection["turnItems"][number]; +type StreamedProjectionTurnItem = Extract< + ProjectionTurnItem, + { readonly type: "assistant_message" | "reasoning" } +>; + +/** A turn item without the fields that move while it streams. */ +export type ThreadStructureTurnItem = + | Exclude + | Omit, "text" | "updatedAt"> + | Omit, "text" | "updatedAt">; + +export type ThreadStructureVisibleTurnItem = Omit< + OrchestrationV2ThreadProjection["visibleTurnItems"][number], + "item" +> & { + readonly item: ThreadStructureTurnItem; +}; + +/** + * A thread projection without the fields that move while a reply streams, so a + * reader of the quiet thread cannot depend on them and go stale. A full + * projection is assignable to it. + */ +export type ThreadStructureThread = Omit< + OrchestrationV2ThreadProjection["thread"], + "updatedAt" | "lastVisitedAt" +>; + +export type ThreadStructure = Omit< + OrchestrationV2ThreadProjection, + "thread" | "turnItems" | "visibleTurnItems" | "updatedAt" +> & { + readonly thread: ThreadStructureThread; + readonly turnItems: ReadonlyArray; + readonly visibleTurnItems: ReadonlyArray; +}; + +export interface EnvironmentThreadStructure { + readonly environmentId: EnvironmentId; + readonly projection: ThreadStructure; +} + export interface ThreadRunSummary { readonly runId: RunId; readonly status: OrchestrationV2RunStatus; @@ -60,11 +103,15 @@ export interface ThreadRuntimeSummary { readonly providerName: string | null; readonly lastError: string | null; readonly lastErrorClass?: OrchestrationV2ProviderFailureClass | null; + readonly lastErrorCode?: string | null; readonly usageLimitResetAt?: string | null; readonly updatedAt: string; } -export function threadRuntimeIsActive(runtime: ThreadRuntimeSummary | null | undefined): boolean { +/** The runtime summary a thread's structure determines; see ThreadStructure. */ +export type ThreadStructureRuntime = Omit; + +export function threadRuntimeIsActive(runtime: ThreadStructureRuntime | null | undefined): boolean { return runtime !== null && runtime !== undefined && threadRunStatusIsActive(runtime.status); } @@ -185,6 +232,7 @@ function shellRuntime(thread: OrchestrationV2ThreadShell): ThreadRuntimeSummary providerName: null, lastError: thread.lastError ?? null, lastErrorClass: thread.lastErrorClass ?? null, + lastErrorCode: thread.lastErrorCode ?? null, usageLimitResetAt: thread.usageLimitResetAt ?? null, updatedAt: iso(thread.updatedAt), }; diff --git a/packages/client-runtime/src/state/presentation.test.ts b/packages/client-runtime/src/state/presentation.test.ts index 0240ee9be05c0..3c3fa20a4a976 100644 --- a/packages/client-runtime/src/state/presentation.test.ts +++ b/packages/client-runtime/src/state/presentation.test.ts @@ -3,6 +3,7 @@ import { EnvironmentId, type ServerConfig } from "@t3tools/contracts"; import * as Option from "effect/Option"; import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity"; +import type { ConnectionCatalogEntry } from "../connection/catalog.ts"; import { AVAILABLE_CONNECTION_STATE, PrimaryConnectionTarget } from "../connection/model.ts"; import { createEnvironmentPresentationAtoms, @@ -88,3 +89,49 @@ describe("environment summary subscriptions", () => { } }); }); + +describe("presentations with providers", () => { + it("leaves out disabled connections until they are re-enabled", () => { + const h = harness(); + const atom = h.full.presentationsWithProvidersAtom; + const withSecond = (enabled: boolean): EnvironmentCatalogState => ({ + isReady: true, + entries: new Map([ + [FIRST, entry(FIRST)], + [SECOND, { ...entry(SECOND), enabled }], + ]), + }); + const stop = h.registry.subscribe(atom, () => {}); + try { + h.registry.set(h.configs(SECOND), config(false, "/second")); + h.registry.set(h.catalog, withSecond(false)); + expect([...h.registry.get(atom).keys()]).toEqual([FIRST]); + expect(h.registry.get(h.full.presentationsAtom).has(SECOND)).toBe(true); + h.registry.set(h.catalog, withSecond(true)); + expect([...h.registry.get(atom).keys()]).toEqual([FIRST, SECOND]); + expect(h.registry.get(atom).get(SECOND)?.serverConfig?.cwd).toBe("/second"); + } finally { + stop(); + h.registry.dispose(); + } + }); + + it("leaves out unsupported environments", () => { + const h = harness(); + const atom = h.full.presentationsWithProvidersAtom; + const stop = h.registry.subscribe(atom, () => {}); + try { + h.registry.set(h.catalog, { + isReady: true, + entries: new Map([ + [FIRST, entry(FIRST)], + [SECOND, { ...entry(SECOND), unsupportedReason: "Server is too old." }], + ]), + }); + expect([...h.registry.get(atom).keys()]).toEqual([FIRST]); + } finally { + stop(); + h.registry.dispose(); + } + }); +}); diff --git a/packages/client-runtime/src/state/presentation.ts b/packages/client-runtime/src/state/presentation.ts index 29782aacfa613..48d27974a2c8c 100644 --- a/packages/client-runtime/src/state/presentation.ts +++ b/packages/client-runtime/src/state/presentation.ts @@ -152,13 +152,17 @@ export function createEnvironmentPresentationAtoms(input: { return previous !== null && mapsEqual(previous, next) ? previous : next; }).pipe(Atom.withLabel("environment-providers")); - // Usage limits ride on provider statuses, so these surfaces rebuild on every push by design. + // Enabled environments only, like serverConfigsAtom: a disabled connection keeps its cached + // config, and its accounts would otherwise count in usage limits. Usage limits ride on provider + // statuses, so these surfaces rebuild on every push by design. const presentationsWithProvidersAtom = Atom.make((get) => { const previous = Option.getOrNull( get.self>(), ); const next = new Map(); - for (const [environmentId, presentation] of get(presentationsAtom)) { + for (const environmentId of enabledEnvironmentIds(get(input.catalogValueAtom))) { + const presentation = get(presentationAtom(environmentId)); + if (presentation === null) continue; const serverConfig = get(input.serverConfigValueAtom(environmentId)); const prior = previous?.get(environmentId); next.set( diff --git a/packages/client-runtime/src/state/pullRequestRouting.ts b/packages/client-runtime/src/state/pullRequestRouting.ts index 73624f97b48bd..2eeb6b0f9355c 100644 --- a/packages/client-runtime/src/state/pullRequestRouting.ts +++ b/packages/client-runtime/src/state/pullRequestRouting.ts @@ -61,7 +61,9 @@ const writes = new Set([ WS_METHODS.pullRequestsSetLabels, ]); const isRef = Schema.is(PullRequestRef); -const hasState = Schema.is(Schema.Struct({ state: PullRequestState })); +const hasState = Schema.is( + Schema.Struct({ state: PullRequestState, isDraft: Schema.optional(Schema.Boolean) }), +); const isInvalidation = Schema.is(PullRequestInvalidateInput); const readTimeout = (environmentId: EnvironmentId) => Effect.timeoutOrElse({ @@ -223,14 +225,13 @@ const invalidateTarget = Effect.fn("PullRequestRouting.invalidateTarget")(functi */ const reportState = ( { allowStale: _allowStale, expectedAccountId: _expectedAccountId, ...reference }: PullRequestRef, - state: PullRequestState, + status: { readonly state: PullRequestState; readonly isDraft?: boolean | undefined }, ) => - request(WS_METHODS.pullRequestsReportState, { reference, state }).pipe( - Effect.timeoutOption("5 seconds"), - Effect.ignore, - Effect.forkDetach, - Effect.asVoid, - ); + request(WS_METHODS.pullRequestsReportState, { + reference, + state: status.state, + ...(status.isDraft === undefined ? {} : { isDraft: status.isDraft }), + }).pipe(Effect.timeoutOption("5 seconds"), Effect.ignore, Effect.forkDetach, Effect.asVoid); /** Credentials stay on their environments. Only a verified host and account cross the wire. */ export function createPullRequestRouter() { @@ -378,7 +379,7 @@ export function createPullRequestRouter() { ).pipe( Effect.tap((result) => id !== origin.target.environmentId && hasState(result) - ? reportState(ref, result.state) + ? reportState(ref, result) : Effect.void, ), Effect.tap(() => diff --git a/packages/client-runtime/src/state/pullRequests.test.ts b/packages/client-runtime/src/state/pullRequests.test.ts index 04b7f56b591c8..a3ca6dc62a15a 100644 --- a/packages/client-runtime/src/state/pullRequests.test.ts +++ b/packages/client-runtime/src/state/pullRequests.test.ts @@ -818,63 +818,68 @@ it.effect.each( ), ); -it.live("keeps source workspace metadata when an alternate answers a detail read", () => - Effect.scoped( - Effect.gen(function* () { - const noted = yield* Deferred.make(); - const clientFor = (local: boolean) => - ({ - [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => - Effect.succeed({ - host: "github.com", - provider: "github", - viewer: "maria-rcks", - accountId: "123", - projectTitle: local ? "local project" : "source project", - workspaceRoot: local ? "/Users/local/repo" : "/srv/source/repo", - }), - [WS_METHODS.pullRequestsDetail]: () => - local - ? Effect.succeed({ - projectId: "local-project", - projectTitle: "local project", - workspaceRoot: "/Users/local/repo", - title: "github title", - state: "closed", - updatedAt: "2026-10-10T01:52:36Z", - }) - : Effect.never, - [WS_METHODS.pullRequestsReportState]: (input: unknown) => - Deferred.succeed(noted, input).pipe(Effect.asVoid), - }) as unknown as WsRpcProtocolClient; - const { environmentRegistry, supervisor } = yield* makeTestRuntime( - clientFor(false), - clientFor(true), - ); - const result = yield* createPullRequestRouter()(WS_METHODS.pullRequestsDetail, { - projectId: ProjectId.make("project-1"), - repository: "acme/web", - number: 7, - }).pipe( - Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), - Effect.provideService(GitHubRoutingPermissions, trustedRouting), - Effect.provideService(RpcPermissionGuard, { authorize: () => Effect.void }), - Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), - ); - expect(result).toEqual({ - projectId: "project-1", - projectTitle: "source project", - workspaceRoot: "/srv/source/repo", - title: "github title", - state: "closed", - updatedAt: "2026-10-10T01:52:36Z", - }); - expect(yield* Deferred.await(noted)).toEqual({ - reference: { projectId: "project-1", repository: "acme/web", number: 7 }, - state: "closed", - }); - }), - ), +it.live.each([undefined, true, false])( + "keeps source workspace metadata and reports draft %s when an alternate answers a detail read", + (isDraft) => + Effect.scoped( + Effect.gen(function* () { + const noted = yield* Deferred.make(); + const clientFor = (local: boolean) => + ({ + [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => + Effect.succeed({ + host: "github.com", + provider: "github", + viewer: "maria-rcks", + accountId: "123", + projectTitle: local ? "local project" : "source project", + workspaceRoot: local ? "/Users/local/repo" : "/srv/source/repo", + }), + [WS_METHODS.pullRequestsDetail]: () => + local + ? Effect.succeed({ + projectId: "local-project", + projectTitle: "local project", + workspaceRoot: "/Users/local/repo", + title: "github title", + state: "closed", + ...(isDraft === undefined ? {} : { isDraft }), + updatedAt: "2026-10-10T01:52:36Z", + }) + : Effect.never, + [WS_METHODS.pullRequestsReportState]: (input: unknown) => + Deferred.succeed(noted, input).pipe(Effect.asVoid), + }) as unknown as WsRpcProtocolClient; + const { environmentRegistry, supervisor } = yield* makeTestRuntime( + clientFor(false), + clientFor(true), + ); + const result = yield* createPullRequestRouter()(WS_METHODS.pullRequestsDetail, { + projectId: ProjectId.make("project-1"), + repository: "acme/web", + number: 7, + }).pipe( + Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), + Effect.provideService(GitHubRoutingPermissions, trustedRouting), + Effect.provideService(RpcPermissionGuard, { authorize: () => Effect.void }), + Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + ); + expect(result).toEqual({ + projectId: "project-1", + projectTitle: "source project", + workspaceRoot: "/srv/source/repo", + title: "github title", + state: "closed", + ...(isDraft === undefined ? {} : { isDraft }), + updatedAt: "2026-10-10T01:52:36Z", + }); + expect(yield* Deferred.await(noted)).toEqual({ + reference: { projectId: "project-1", repository: "acme/web", number: 7 }, + state: "closed", + ...(isDraft === undefined ? {} : { isDraft }), + }); + }), + ), ); it.live( diff --git a/packages/client-runtime/src/state/threadDetail.test.ts b/packages/client-runtime/src/state/threadDetail.test.ts new file mode 100644 index 0000000000000..6899840305697 --- /dev/null +++ b/packages/client-runtime/src/state/threadDetail.test.ts @@ -0,0 +1,175 @@ +import { + EnvironmentId, + MessageId, + RunId, + TurnItemId, + type OrchestrationV2ThreadProjection, + type OrchestrationV2TurnItem, +} from "@t3tools/contracts"; +import { describe, expect, it } from "@effect/vitest"; +import * as DateTime from "effect/DateTime"; +import * as Option from "effect/Option"; +import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity"; + +import { v2Now, v2Projection, v2ThreadId } from "./orchestrationV2TestFixtures.ts"; +import { createEnvironmentThreadDetailAtoms, sameThreadStructure } from "./threadDetail.ts"; +import { EMPTY_ENVIRONMENT_THREAD_STATE } from "./threadState.ts"; + +const later = DateTime.makeUnsafe("2026-06-20T00:00:01.000Z"); + +const assistant: OrchestrationV2TurnItem = { + id: TurnItemId.make("item-assistant"), + threadId: v2ThreadId, + runId: RunId.make("run-1"), + nodeId: null, + providerThreadId: null, + providerTurnId: null, + nativeItemRef: null, + parentItemId: null, + ordinal: 1, + status: "running", + title: null, + startedAt: v2Now, + completedAt: null, + updatedAt: v2Now, + type: "assistant_message", + messageId: MessageId.make("message-assistant"), + text: "Hel", + streaming: true, +}; + +function withItem(item: OrchestrationV2TurnItem, updatedAt = v2Now) { + return { + ...v2Projection, + turnItems: [item], + visibleTurnItems: [ + { + position: 0, + visibility: "local" as const, + sourceThreadId: v2ThreadId, + sourceItemId: item.id, + item, + }, + ], + updatedAt, + }; +} + +describe("sameThreadStructure", () => { + const before = withItem(assistant); + + it("holds through streamed text", () => { + const next = withItem({ ...assistant, text: "Hello", updatedAt: later }, later); + expect(sameThreadStructure(before, next)).toBe(true); + }); + + it("sees the item finish streaming", () => { + const next = withItem({ ...assistant, text: "Hello", streaming: false, status: "completed" }); + expect(sameThreadStructure(before, next)).toBe(false); + }); + + it("sees a new item", () => { + const next = { ...before, turnItems: [assistant, { ...assistant, id: TurnItemId.make("b") }] }; + expect(sameThreadStructure(before, next)).toBe(false); + }); + + it("sees other fields change on a streaming item", () => { + const next = withItem({ ...assistant, text: "Hello", title: "Answer" }); + expect(sameThreadStructure(before, next)).toBe(false); + }); + + it("holds through a visit mark", () => { + const thread = { ...before.thread, lastVisitedAt: later, updatedAt: later }; + expect(sameThreadStructure(before, { ...before, thread })).toBe(true); + }); + + it("sees other thread changes", () => { + const thread = { ...before.thread, title: "Renamed" }; + expect(sameThreadStructure(before, { ...before, thread })).toBe(false); + }); + + it("sees changes outside the turn items", () => { + expect(sameThreadStructure(before, { ...before, runs: [] })).toBe(false); + }); +}); + +describe("lastVisitedAtAtom", () => { + it("follows a visit mark the quiet thread holds through", () => { + const projection = withItem(assistant); + const state = Atom.make( + AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(projection) }), + ); + const details = createEnvironmentThreadDetailAtoms(() => state); + const ref = { environmentId: EnvironmentId.make("env"), threadId: v2ThreadId }; + const registry = AtomRegistry.make(); + const unmountQuiet = registry.mount(details.quietThreadAtom(ref)); + const unmountVisit = registry.mount(details.lastVisitedAtAtom(ref)); + const quietBefore = registry.get(details.quietThreadAtom(ref)); + + const visited = { ...projection, thread: { ...projection.thread, lastVisitedAt: later } }; + registry.set( + state, + AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(visited) }), + ); + + expect(registry.get(details.quietThreadAtom(ref))).toBe(quietBefore); + expect(registry.get(details.lastVisitedAtAtom(ref))).toBe(later); + unmountQuiet(); + unmountVisit(); + registry.dispose(); + }); +}); + +describe("quietThreadAtom", () => { + const ref = { environmentId: EnvironmentId.make("environment-1"), threadId: v2ThreadId }; + + function setup(initial: OrchestrationV2ThreadProjection) { + const stateAtom = Atom.make( + AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(initial) }), + ); + const quietAtom = createEnvironmentThreadDetailAtoms(() => stateAtom).quietThreadAtom(ref); + const registry = AtomRegistry.make(); + registry.mount(quietAtom); + return { + read: () => registry.get(quietAtom), + push: (projection: OrchestrationV2ThreadProjection) => + registry.set( + stateAtom, + AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(projection) }), + ), + }; + } + + it("keeps the same value through streamed text", () => { + const thread = setup(withItem(assistant)); + const first = thread.read(); + thread.push(withItem({ ...assistant, text: "Hello", updatedAt: later }, later)); + thread.push(withItem({ ...assistant, text: "Hello there", updatedAt: later }, later)); + expect(thread.read()).toBe(first); + }); + + it("passes a structure change on", () => { + const thread = setup(withItem(assistant)); + const first = thread.read(); + thread.push(withItem({ ...assistant, text: "Hello", updatedAt: later }, later)); + thread.push(withItem({ ...assistant, text: "Hello", status: "completed", streaming: false })); + const next = thread.read(); + expect(next).not.toBe(first); + expect(next?.projection.turnItems[0]).toMatchObject({ status: "completed", streaming: false }); + }); + + it("leaves out the fields that streaming moves", () => { + const thread = setup(withItem(assistant)); + const projection = thread.read()?.projection; + expect(projection).not.toHaveProperty("updatedAt"); + expect(projection?.thread).not.toHaveProperty("updatedAt"); + expect(projection?.thread).not.toHaveProperty("lastVisitedAt"); + expect(projection?.turnItems[0]).not.toHaveProperty("text"); + expect(projection?.turnItems[0]).not.toHaveProperty("updatedAt"); + expect(projection?.visibleTurnItems[0]?.item).not.toHaveProperty("text"); + expect(projection?.visibleTurnItems[0]?.item).toMatchObject({ + messageId: assistant.messageId, + streaming: true, + }); + }); +}); diff --git a/packages/client-runtime/src/state/threadDetail.ts b/packages/client-runtime/src/state/threadDetail.ts index 89ec5cc74ce22..57bdd36027aae 100644 --- a/packages/client-runtime/src/state/threadDetail.ts +++ b/packages/client-runtime/src/state/threadDetail.ts @@ -1,5 +1,11 @@ -import type { OrchestrationV2ThreadProjection, ScopedThreadRef } from "@t3tools/contracts"; +import { + OrchestrationV2AppThread, + OrchestrationV2TurnItem, + type OrchestrationV2ThreadProjection, + type ScopedThreadRef, +} from "@t3tools/contracts"; import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { AsyncResult, Atom } from "effect/reactivity"; import { @@ -12,7 +18,14 @@ import { deriveThreadCheckpointSummaries, type ThreadCheckpointSummary, } from "./threadCheckpoints.ts"; -import type { EnvironmentThread } from "./models.ts"; +import type { + EnvironmentThread, + EnvironmentThreadStructure, + ThreadStructure, + ThreadStructureThread, + ThreadStructureTurnItem, + ThreadStructureVisibleTurnItem, +} from "./models.ts"; import { EMPTY_ENVIRONMENT_THREAD_STATE, type EnvironmentThreadState } from "./threadState.ts"; import { createQuestionHistoryProjector, @@ -24,8 +37,153 @@ import { arrayElementsEqual, parseThreadKey, threadKey } from "./entities.ts"; const EMPTY_VISIBLE_TURN_ITEMS: OrchestrationV2ThreadProjection["visibleTurnItems"] = Object.freeze( [], ); +const EMPTY_THREAD_STRUCTURE_TURN_ITEMS: ReadonlyArray = + Object.freeze([]); const EMPTY_CHECKPOINT_SUMMARIES: ReadonlyArray = Object.freeze([]); +type TurnItem = OrchestrationV2ThreadProjection["turnItems"][number]; +type VisibleTurnItem = OrchestrationV2ThreadProjection["visibleTurnItems"][number]; +type AppThread = OrchestrationV2ThreadProjection["thread"]; + +// Items are stripped once each: unchanged items keep their identity across +// structure changes, which the incremental timeline relies on. +const structureTurnItems = new WeakMap(); +const structureVisibleTurnItems = new WeakMap(); + +function toThreadStructureTurnItem(item: TurnItem): ThreadStructureTurnItem { + if (item.type !== "assistant_message" && item.type !== "reasoning") return item; + let structure = structureTurnItems.get(item); + if (structure === undefined) { + const { text: _text, updatedAt: _updatedAt, ...rest } = item; + structure = rest; + structureTurnItems.set(item, structure); + } + return structure; +} + +function toThreadStructureVisibleTurnItem( + visibleItem: VisibleTurnItem, +): ThreadStructureVisibleTurnItem { + const item = toThreadStructureTurnItem(visibleItem.item); + if (item === visibleItem.item) return visibleItem; + let structure = structureVisibleTurnItems.get(visibleItem); + if (structure === undefined) { + structure = { ...visibleItem, item }; + structureVisibleTurnItems.set(visibleItem, structure); + } + return structure; +} + +/** Whether the structure kept every field of this row: true for all but streamed items. */ +export function isCompleteTurnItemRow(row: ThreadStructureVisibleTurnItem): row is VisibleTurnItem { + return row.item.type !== "assistant_message" && row.item.type !== "reasoning"; +} + +// The open thread is marked visited several times while a reply streams. Visit +// state is read from the shell, or from lastVisitedAtAtom while the thread is +// not in the shell index yet, so a mark is not structure. +function toThreadStructureThread(thread: AppThread): ThreadStructureThread { + const { updatedAt: _updatedAt, lastVisitedAt: _lastVisitedAt, ...structure } = thread; + return structure; +} + +function toThreadStructure(projection: OrchestrationV2ThreadProjection): ThreadStructure { + const { updatedAt: _updatedAt, ...structure } = projection; + return { + ...structure, + thread: toThreadStructureThread(projection.thread), + turnItems: projection.turnItems.map(toThreadStructureTurnItem), + visibleTurnItems: projection.visibleTurnItems.map(toThreadStructureVisibleTurnItem), + }; +} + +const sameTurnItem = Schema.toEquivalence(OrchestrationV2TurnItem); +const sameAppThread = Schema.toEquivalence(OrchestrationV2AppThread); + +// Overlays previous's structure on next, so the fields the strip functions +// remove are exactly the ones ignored here. +function sameApartFromStripped( + same: (left: T, right: T) => boolean, + previous: T, + previousStructure: object, + next: T, +): boolean { + return ( + Object.keys(previous).length === Object.keys(next).length && + same(Object.assign({}, next, previousStructure), next) + ); +} + +function sameTurnItemStructure(previous: TurnItem, next: TurnItem): boolean { + if (previous === next) return true; + const structure = toThreadStructureTurnItem(previous); + if (previous.id !== next.id || previous.type !== next.type || structure === previous) { + return false; + } + return sameApartFromStripped(sameTurnItem, previous, structure, next); +} + +/** + * Whether `previous` and `next` have the same ThreadStructure: they differ + * only by text streamed into existing assistant and reasoning items or by a + * visit mark. The same items exist, with the same status, and every other + * field is unchanged. + */ +export function sameThreadStructure( + previous: OrchestrationV2ThreadProjection, + next: OrchestrationV2ThreadProjection, +): boolean { + if (previous === next) return true; + for (const key of Object.keys(next) as Array) { + if ( + key === "thread" || + key === "turnItems" || + key === "visibleTurnItems" || + key === "updatedAt" + ) { + continue; + } + if (previous[key] !== next[key]) return false; + } + if ( + previous.thread !== next.thread && + !sameApartFromStripped( + sameAppThread, + previous.thread, + toThreadStructureThread(previous.thread), + next.thread, + ) + ) { + return false; + } + if ( + previous.turnItems.length !== next.turnItems.length || + previous.visibleTurnItems.length !== next.visibleTurnItems.length + ) { + return false; + } + for (let index = 0; index < next.turnItems.length; index += 1) { + if (!sameTurnItemStructure(previous.turnItems[index]!, next.turnItems[index]!)) { + return false; + } + } + for (let index = 0; index < next.visibleTurnItems.length; index += 1) { + const before = previous.visibleTurnItems[index]!; + const after = next.visibleTurnItems[index]!; + if (before === after) continue; + if ( + before.position !== after.position || + before.visibility !== after.visibility || + before.sourceThreadId !== after.sourceThreadId || + before.sourceItemId !== after.sourceItemId || + !sameTurnItemStructure(before.item, after.item) + ) { + return false; + } + } + return true; +} + export function createEnvironmentThreadDetailAtoms( threadStateAtom: ( environmentId: ScopedThreadRef["environmentId"], @@ -55,6 +213,32 @@ export function createEnvironmentThreadDetailAtoms( }).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-thread:${key}`)); }); + // Streamed assistant and reasoning text replaces an item several times a + // second. Readers of the thread's structure (runs, requests, which items + // exist and their status) keep the previous value through those updates. + const quietThreadAtomFamily = Atom.family((key: string) => { + let previousProjection: OrchestrationV2ThreadProjection | null = null; + let previous: EnvironmentThreadStructure | null = null; + return Atom.make((get) => { + const next = get(threadAtomFamily(key)); + if (next === null) { + previousProjection = null; + previous = null; + } else if ( + previousProjection === null || + !sameThreadStructure(previousProjection, next.projection) + ) { + previous = { + environmentId: next.environmentId, + projection: toThreadStructure(next.projection), + }; + } + // Compared against the latest projection, a delta only compares the item it replaced. + previousProjection = next?.projection ?? null; + return previous; + }).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-quiet-thread:${key}`)); + }); + const visibleTurnItemsAtomFamily = Atom.family((key: string) => { const projectQuestionHistory = createQuestionHistoryProjector(); return Atom.make((get): OrchestrationV2ThreadProjection["visibleTurnItems"] => { @@ -63,6 +247,19 @@ export function createEnvironmentThreadDetailAtoms( }).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-thread-visible-turn-items:${key}`)); }); + const quietVisibleTurnItemsAtomFamily = Atom.family((key: string) => { + const projectQuestionHistory = createQuestionHistoryProjector(); + return Atom.make((get) => { + const thread = get(quietThreadAtomFamily(key)); + return thread === null + ? EMPTY_THREAD_STRUCTURE_TURN_ITEMS + : projectQuestionHistory(thread.projection); + }).pipe( + Atom.setIdleTTL(0), + Atom.withLabel(`environment-quiet-thread-visible-turn-items:${key}`), + ); + }); + const queueWorkflowAtomFamily = Atom.family((key: string) => { let previous: Pick< OrchestrationV2ThreadProjection, @@ -148,6 +345,17 @@ export function createEnvironmentThreadDetailAtoms( }).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-thread-checkpoint-summaries:${key}`)); }); + const lastVisitedAtAtomFamily = Atom.family((key: string) => + Atom.make( + (get) => + Option.getOrNull(get(threadStateValueAtomFamily(key)).data)?.thread.lastVisitedAt ?? null, + ).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-thread-last-visited:${key}`)), + ); + const updatedAtAtomFamily = Atom.family((key: string) => + Atom.make( + (get) => Option.getOrNull(get(threadStateValueAtomFamily(key)).data)?.updatedAt ?? null, + ).pipe(Atom.setIdleTTL(0), Atom.withLabel(`environment-thread-updated-at:${key}`)), + ); const worktreePathAtomFamily = Atom.family((key: string) => Atom.make( (get) => @@ -210,6 +418,8 @@ export function createEnvironmentThreadDetailAtoms( return { worktreePathAtom: (ref: ScopedThreadRef) => worktreePathAtomFamily(threadKey(ref)), + updatedAtAtom: (ref: ScopedThreadRef) => updatedAtAtomFamily(threadKey(ref)), + lastVisitedAtAtom: (ref: ScopedThreadRef) => lastVisitedAtAtomFamily(threadKey(ref)), pendingRequestsAtom: (ref: ScopedThreadRef) => pendingRequestsAtomFamily(threadKey(ref)), queueWorkflowAtom: (ref: ScopedThreadRef) => queueWorkflowAtomFamily(threadKey(ref)), queuedCountAtom: (ref: ScopedThreadRef) => queuedCountAtomFamily(threadKey(ref)), @@ -218,7 +428,10 @@ export function createEnvironmentThreadDetailAtoms( checkpointSummariesAtomFamily(threadKey(ref)), stateAtom: (ref: ScopedThreadRef) => threadStateValueAtomFamily(threadKey(ref)), threadAtom: (ref: ScopedThreadRef) => threadAtomFamily(threadKey(ref)), + quietThreadAtom: (ref: ScopedThreadRef) => quietThreadAtomFamily(threadKey(ref)), visibleTurnItemsAtom: (ref: ScopedThreadRef) => visibleTurnItemsAtomFamily(threadKey(ref)), + quietVisibleTurnItemsAtom: (ref: ScopedThreadRef) => + quietVisibleTurnItemsAtomFamily(threadKey(ref)), statusAtom: (ref: ScopedThreadRef) => statusAtomFamily(threadKey(ref)), errorAtom: (ref: ScopedThreadRef) => errorAtomFamily(threadKey(ref)), historyAtom: (ref: ScopedThreadRef) => historyAtomFamily(threadKey(ref)), diff --git a/packages/client-runtime/src/state/threadExecution.ts b/packages/client-runtime/src/state/threadExecution.ts index 8c27b2373c037..accf5c98df9a9 100644 --- a/packages/client-runtime/src/state/threadExecution.ts +++ b/packages/client-runtime/src/state/threadExecution.ts @@ -30,6 +30,8 @@ import { threadRuntimeIsActive, type ThreadRunSummary, type ThreadRuntimeSummary, + type ThreadStructure, + type ThreadStructureRuntime, } from "./models.ts"; import { formatSubagentDisplayTitle } from "./subagentDisplay.ts"; @@ -37,7 +39,7 @@ const ACTIVITY_RUN_STATUSES = new Set(["preparing", "starting", "running", "wait const INTERRUPTIBLE_RUN_STATUSES = new Set(["preparing", "starting", "running"]); function latestMatchingRun( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, predicate: (run: OrchestrationV2ThreadProjection["runs"][number]) => boolean, ): OrchestrationV2ThreadProjection["runs"][number] | null { return projection.runs.reduce( @@ -50,7 +52,7 @@ function latestMatchingRun( } function summarizeThreadRun( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, run: OrchestrationV2ThreadProjection["runs"][number], ): ThreadRunSummary { const turnStatus = ACTIVITY_RUN_STATUSES.has(run.status) @@ -74,7 +76,7 @@ function summarizeThreadRun( } function presentedUsageLimitRun( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, ): OrchestrationV2ThreadProjection["runs"][number] | null { const providerSession = projection.providerSessions.findLast( (session) => session.providerInstanceId === projection.thread.providerInstanceId, @@ -87,21 +89,17 @@ function presentedUsageLimitRun( } function presentedLatestRun( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, ): OrchestrationV2ThreadProjection["runs"][number] | null { return presentedUsageLimitRun(projection) ?? latestUnheldRun(projection.runs); } -export function deriveLatestThreadRun( - projection: OrchestrationV2ThreadProjection, -): ThreadRunSummary | null { +export function deriveLatestThreadRun(projection: ThreadStructure): ThreadRunSummary | null { const run = presentedLatestRun(projection); return run === null ? null : summarizeThreadRun(projection, run); } -export function deriveThreadActivityRun( - projection: OrchestrationV2ThreadProjection, -): ThreadRunSummary | null { +export function deriveThreadActivityRun(projection: ThreadStructure): ThreadRunSummary | null { const run = latestMatchingRun(projection, (candidate) => ACTIVITY_RUN_STATUSES.has(candidate.status)) ?? presentedLatestRun(projection); @@ -112,9 +110,7 @@ export function deriveThreadActivityRun( * Provider-native subagent threads never get app runs: their work is a runless * root turn whose status follows the subagent. */ -export function deriveRunlessWorkStartedAt( - projection: OrchestrationV2ThreadProjection, -): string | null { +export function deriveRunlessWorkStartedAt(projection: ThreadStructure): string | null { const status = deriveProviderSubagentStatus(projection); return status !== null && isOrchestrationV2WorkActive(status.status) ? status.startedAt : null; } @@ -126,7 +122,7 @@ export interface ProviderSubagentStatus { } export function deriveProviderSubagentStatus( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure, ): ProviderSubagentStatus | null { if (!isProviderNativeSubagentThread(projection.thread)) return null; const node = projection.nodes.findLast( @@ -140,9 +136,7 @@ export function deriveProviderSubagentStatus( }; } -export function deriveReportedModelSelection( - projection: OrchestrationV2ThreadProjection, -): ModelSelection | null { +export function deriveReportedModelSelection(projection: ThreadStructure): ModelSelection | null { const providerThread = projection.providerThreads.find( (candidate) => candidate.id === projection.thread.activeProviderThreadId && @@ -215,8 +209,18 @@ export function formatProviderSubagentStatus( } export function deriveThreadRuntime( - projection: OrchestrationV2ThreadProjection, + projection: ThreadStructure & Pick, ): ThreadRuntimeSummary | null { + const runtime = deriveThreadStructureRuntime(projection); + return runtime === null + ? null + : { ...runtime, updatedAt: DateTime.formatIso(projection.updatedAt) }; +} + +/** The runtime summary a thread's structure determines: all of it but `updatedAt`. */ +export function deriveThreadStructureRuntime( + projection: ThreadStructure, +): ThreadStructureRuntime | null { const latestRun = deriveLatestThreadRun(projection); const providerSession = projection.providerSessions.findLast( (session) => session.providerInstanceId === projection.thread.providerInstanceId, @@ -264,12 +268,11 @@ export function deriveThreadRuntime( latestRootProviderFailure(latestRunProjection, projection.turnItems), providerSession?.lastError ?? null, ), - updatedAt: DateTime.formatIso(projection.updatedAt), }; } export function threadRuntimeHasInterruptibleRun( - runtime: ThreadRuntimeSummary | null | undefined, + runtime: ThreadStructureRuntime | null | undefined, ): boolean { return ( threadRuntimeIsActive(runtime) && diff --git a/packages/client-runtime/src/state/threadRequests.ts b/packages/client-runtime/src/state/threadRequests.ts index d3fe73444966b..5a9289fe396e0 100644 --- a/packages/client-runtime/src/state/threadRequests.ts +++ b/packages/client-runtime/src/state/threadRequests.ts @@ -10,6 +10,7 @@ import type { } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import type { AtomCommandResult } from "./runtime.ts"; +import type { ThreadStructure, ThreadStructureVisibleTurnItem } from "./models.ts"; export type ApprovalResponder = ( requestId: RuntimeRequestId, @@ -93,7 +94,7 @@ export function seedUserInputDraftAnswers, + projection: Pick, ): PendingThreadRequests { const approvals: ThreadPendingApproval[] = []; const userInputs: ThreadPendingUserInput[] = []; @@ -147,21 +148,25 @@ export function derivePendingThreadRequests( return { approvals, userInputs }; } -export function createQuestionHistoryProjector() { - type Rows = OrchestrationV2ThreadProjection["visibleTurnItems"]; +export function createQuestionHistoryProjector< + Row extends ThreadStructureVisibleTurnItem = + OrchestrationV2ThreadProjection["visibleTurnItems"][number], +>() { + type Rows = ReadonlyArray; type Requests = OrchestrationV2ThreadProjection["runtimeRequests"]; let previousRows: Rows | undefined; let previousRequests: Requests | undefined; let previousResult: Rows | undefined; - const cache = new WeakMap(); - return ( - projection: Pick, - ): Rows => { + const cache = new WeakMap(); + return (projection: { + readonly visibleTurnItems: Rows; + readonly runtimeRequests: Requests; + }): Rows => { const { visibleTurnItems: rows, runtimeRequests: requests } = projection; if (rows === previousRows && requests === previousRequests && previousResult) return previousResult; const byId = new Map(requests.map((request) => [request.id, request])); - let result: Rows[number][] | undefined; + let result: Row[] | undefined; for (const [index, row] of rows.entries()) { const item = row.item; const request = diff --git a/packages/client-runtime/src/state/threadWorkflows.ts b/packages/client-runtime/src/state/threadWorkflows.ts index 5adec9e657edb..21139a2c4549e 100644 --- a/packages/client-runtime/src/state/threadWorkflows.ts +++ b/packages/client-runtime/src/state/threadWorkflows.ts @@ -2,11 +2,12 @@ import type { ChatAttachment, OrchestrationV2ProjectedTurnItem, OrchestrationV2ProviderCapabilities, - OrchestrationV2ThreadProjection, } from "@t3tools/contracts"; import { copySorted } from "@t3tools/shared/Array"; -type Projection = OrchestrationV2ThreadProjection; +import type { ThreadStructure } from "./models.ts"; + +type Projection = ThreadStructure; type Run = Projection["runs"][number]; type Message = Projection["messages"][number]; type ProviderSession = Projection["providerSessions"][number]; diff --git a/packages/client-runtime/src/state/turnItemPresentation.ts b/packages/client-runtime/src/state/turnItemPresentation.ts index 4e70159dc36c8..400b07005fbc7 100644 --- a/packages/client-runtime/src/state/turnItemPresentation.ts +++ b/packages/client-runtime/src/state/turnItemPresentation.ts @@ -4,6 +4,8 @@ import { type OrchestrationV2TurnItem, } from "@t3tools/contracts"; +import type { ThreadStructureTurnItem } from "./models.ts"; + const WORKSPACE_PREPARATION_INPUT = "Preparing workspace"; export function turnItemIsWorkspacePreparation(item: OrchestrationV2TurnItem): boolean { @@ -17,7 +19,7 @@ export function turnItemIsWorkspacePreparation(item: OrchestrationV2TurnItem): b export function workspacePreparationRetryRunIds( runs: ReadonlyArray>, - items: ReadonlyArray, + items: ReadonlyArray, ): ReadonlySet { const failedRuns = new Set( runs.flatMap((run) => diff --git a/packages/client-runtime/src/state/vcs.test.ts b/packages/client-runtime/src/state/vcs.test.ts index b93f0d832fee1..0c839e188e106 100644 --- a/packages/client-runtime/src/state/vcs.test.ts +++ b/packages/client-runtime/src/state/vcs.test.ts @@ -3,16 +3,21 @@ import { EnvironmentId, AuthSourceControlWriteScope, WS_METHODS, + VcsProcessExitError, type VcsListRefsResult, } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; +import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; import * as Option from "effect/Option"; +import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; +import * as TestClock from "effect/testing/TestClock"; import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity"; import { @@ -402,63 +407,167 @@ describe("cached VCS refs", () => { ), ); - it.effect("cancels an in-flight refresh when the connection generation changes", () => + it.effect("waits silently for a session while the connection phase is still connected", () => Effect.scoped( Effect.gen(function* () { + const logs: unknown[] = []; + const logger = Logger.make(({ message }) => logs.push(message)); + const liveSession = yield* SubscriptionRef.make(Option.none()); + const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ + target: TARGET, + state: yield* SubscriptionRef.make(CONNECTED_CONNECTION_STATE), + session: liveSession, + prepared: yield* SubscriptionRef.make(Option.none()), + connect: Effect.void, + disconnect: Effect.void, + retryNow: Effect.void, + } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); + const refs = yield* Stream.unwrap( + makeCachedVcsRefsChanges({ cwd: "/repo", limit: 100 }).pipe( + Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + Effect.provideService( + Persistence.EnvironmentCacheStore, + cacheWithRefs(Option.some(CACHED_REFS)), + ), + ), + ).pipe( + Stream.take(2), + Stream.runCollect, + Effect.provide(Logger.layer([logger], { mergeWithExisting: false })), + Effect.forkChild({ startImmediately: true }), + ); + + yield* TestClock.adjust("1 minute"); + expect(logs).toEqual([]); + + const client = { + [WS_METHODS.vcsListRefs]: () => Effect.succeed(LIVE_REFS), + } as unknown as WsRpcProtocolClient; + yield* SubscriptionRef.set(liveSession, Option.some(session(client))); + expect(yield* Fiber.join(refs)).toEqual([CACHED_REFS, LIVE_REFS]); + expect(logs).toEqual([]); + }), + ), + ); + + it.effect("cancels an in-flight refresh and restarts when the RPC session changes", () => + Effect.scoped( + Effect.gen(function* () { + const started = yield* Deferred.make(); + const interrupted = yield* Deferred.make(); const calls = yield* Ref.make(0); const interruptions = yield* Ref.make(0); - const connectionState = yield* SubscriptionRef.make(CONNECTED_CONNECTION_STATE); const client = { [WS_METHODS.vcsListRefs]: () => Ref.updateAndGet(calls, (count) => count + 1).pipe( Effect.flatMap((count) => count === 1 - ? Effect.never.pipe( + ? Deferred.succeed(started, undefined).pipe( + Effect.andThen(Effect.never), Effect.onInterrupt(() => - Ref.update(interruptions, (interruptions) => interruptions + 1), + Ref.update(interruptions, (interruptions) => interruptions + 1).pipe( + Effect.andThen(Deferred.succeed(interrupted, undefined)), + ), ), ) : Effect.succeed(LIVE_REFS), ), ), } as unknown as WsRpcProtocolClient; + const liveSession = yield* SubscriptionRef.make(Option.some(session(client))); const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ target: TARGET, - state: connectionState, - session: yield* SubscriptionRef.make(Option.some(session(client))), + state: yield* SubscriptionRef.make(CONNECTED_CONNECTION_STATE), + session: liveSession, prepared: yield* SubscriptionRef.make(Option.none()), connect: Effect.void, disconnect: Effect.void, retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); - const result = Stream.unwrap( + const fiber = yield* Stream.unwrap( makeCachedVcsRefsChanges({ cwd: "/repo", limit: 100 }).pipe( Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), Effect.provideService(Persistence.EnvironmentCacheStore, cacheWithRefs(Option.none())), ), - ).pipe(Stream.runHead); - const fiber = yield* Effect.forkChild(result); - - for (let attempt = 0; attempt < 100 && (yield* Ref.get(calls)) < 1; attempt += 1) { - yield* Effect.yieldNow; - } - yield* SubscriptionRef.set(connectionState, AVAILABLE_CONNECTION_STATE); - for (let attempt = 0; attempt < 100 && (yield* Ref.get(interruptions)) < 1; attempt += 1) { - yield* Effect.yieldNow; - } + ).pipe(Stream.runHead, Effect.forkChild); + + yield* Deferred.await(started); + yield* SubscriptionRef.set(liveSession, Option.none()); + yield* Deferred.await(interrupted); expect(yield* Ref.get(interruptions)).toBe(1); - yield* SubscriptionRef.set(connectionState, { - ...CONNECTED_CONNECTION_STATE, - generation: 2, - }); + yield* SubscriptionRef.set(liveSession, Option.some(session(client))); expect(Option.getOrThrow(yield* Fiber.join(fiber))).toEqual(LIVE_REFS); expect(yield* Ref.get(calls)).toBe(2); }), ), ); + it.effect("retries Git failures and cancels pending retries when the session is removed", () => + Effect.scoped( + Effect.gen(function* () { + const calls = yield* Ref.make(0); + const warnings = yield* Queue.unbounded(); + const logger = Logger.make(({ message }) => Queue.offerUnsafe(warnings, message)); + const failure = new VcsProcessExitError({ + operation: "listRefs", + command: "git", + cwd: "/repo", + exitCode: 128, + detail: "Could not read refs.", + }); + const client = { + [WS_METHODS.vcsListRefs]: () => + Ref.updateAndGet(calls, (count) => count + 1).pipe( + Effect.flatMap((count) => + count < 3 ? Effect.fail(failure) : Effect.succeed(LIVE_REFS), + ), + ), + } as unknown as WsRpcProtocolClient; + const liveSession = yield* SubscriptionRef.make(Option.some(session(client))); + const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ + target: TARGET, + state: yield* SubscriptionRef.make(CONNECTED_CONNECTION_STATE), + session: liveSession, + prepared: yield* SubscriptionRef.make(Option.none()), + connect: Effect.void, + disconnect: Effect.void, + retryNow: Effect.void, + } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); + const refs = yield* Stream.unwrap( + makeCachedVcsRefsChanges({ cwd: "/repo", limit: 100 }).pipe( + Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + Effect.provideService( + Persistence.EnvironmentCacheStore, + cacheWithRefs(Option.some(CACHED_REFS)), + ), + ), + ).pipe( + Stream.take(2), + Stream.runCollect, + Effect.provide(Logger.layer([logger], { mergeWithExisting: false })), + Effect.forkChild({ startImmediately: true }), + ); + + yield* Queue.take(warnings); + expect(yield* Ref.get(calls)).toBe(1); + yield* TestClock.adjust("1 second"); + expect(yield* Ref.get(calls)).toBe(2); + expect(yield* Queue.size(warnings)).toBe(1); + + yield* SubscriptionRef.set(liveSession, Option.none()); + yield* TestClock.adjust("1 minute"); + expect(yield* Ref.get(calls)).toBe(2); + expect(yield* Queue.size(warnings)).toBe(1); + + yield* SubscriptionRef.set(liveSession, Option.some(session(client))); + expect(yield* Fiber.join(refs)).toEqual([CACHED_REFS, LIVE_REFS]); + expect(yield* Ref.get(calls)).toBe(3); + }), + ), + ); + it.effect("emits persisted refs before a live refresh", () => Effect.scoped( Effect.gen(function* () { diff --git a/packages/client-runtime/src/state/vcs.ts b/packages/client-runtime/src/state/vcs.ts index 96b00334d0fa3..0aa2490db3258 100644 --- a/packages/client-runtime/src/state/vcs.ts +++ b/packages/client-runtime/src/state/vcs.ts @@ -179,17 +179,16 @@ export const makeCachedVcsRefsChanges = Effect.fn("CachedVcsRefsState.makeChange }), ), ); - const refreshedRefs = Stream.concat( - Stream.fromEffect(SubscriptionRef.get(supervisor.state)), - SubscriptionRef.changes(supervisor.state), - ).pipe( - Stream.map((connection) => (connection.phase === "connected" ? connection.generation : null)), + const refreshedRefs = SubscriptionRef.changes(supervisor.session).pipe( + Stream.map(Option.getOrNull), Stream.changes, - Stream.switchMap((generation) => - generation === null + Stream.switchMap((session) => + session === null ? Stream.empty : Stream.fromEffect( refresh().pipe( + // Session removal can precede switchMap cancelling this refresh. + Effect.catchTags({ EnvironmentRpcUnavailableError: () => Effect.never }), Effect.tapError((error) => Effect.logWarning("Could not refresh Git refs.").pipe( Effect.annotateLogs({ diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 8476254bf21f3..a2a0d2f184590 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -21,7 +21,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/contracts/src/assets.ts b/packages/contracts/src/assets.ts index e21a71dd7dd25..4726b4d7b92ab 100644 --- a/packages/contracts/src/assets.ts +++ b/packages/contracts/src/assets.ts @@ -75,6 +75,8 @@ export const AssetCreateUrlResult = Schema.Struct({ TrimmedNonEmptyString.check(Schema.isMaxLength(ASSET_PATH_MAX_LENGTH)), ), imageDimensions: Schema.optional(AssetImageDimensions), + /** Changes only when the file's contents or identity change, unlike `relativeUrl`, which is re-signed on every mint. */ + fileRevision: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(256))), }); export type AssetCreateUrlResult = typeof AssetCreateUrlResult.Type; diff --git a/packages/contracts/src/environment.ts b/packages/contracts/src/environment.ts index 4272c9d12d94b..18d09c304a83e 100644 --- a/packages/contracts/src/environment.ts +++ b/packages/contracts/src/environment.ts @@ -174,6 +174,7 @@ export type RepositoryIdentityLocator = typeof RepositoryIdentityLocator.Type; export const RepositoryOrigin = Schema.Struct({ canonicalKey: TrimmedNonEmptyString, + remoteUrl: Schema.optionalKey(TrimmedNonEmptyString), displayName: Schema.optionalKey(TrimmedNonEmptyString), }); export type RepositoryOrigin = typeof RepositoryOrigin.Type; diff --git a/packages/contracts/src/git.ts b/packages/contracts/src/git.ts index 0976dcb07d5af..cc5fcd014580b 100644 --- a/packages/contracts/src/git.ts +++ b/packages/contracts/src/git.ts @@ -361,6 +361,8 @@ export const GitCommandFailureReason = Schema.Literals([ "host_key_unverified", "not_a_repository", "path_already_exists", + "push_rejected_by_remote", + "push_rejected_non_fast_forward", "ref_not_found", "remote_unreachable", "tag_would_be_clobbered", diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index 4684575db4f8f..bb6fdfe31b635 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -1838,6 +1838,7 @@ export const OrchestrationV2ThreadShell = Schema.Struct({ status: OrchestrationV2ShellThreadStatus, lastError: Schema.optional(Schema.NullOr(Schema.String)), lastErrorClass: Schema.optional(Schema.NullOr(OrchestrationV2ProviderFailureClass)), + lastErrorCode: Schema.optional(Schema.NullOr(Schema.String)), usageLimitResetAt: Schema.optional(Schema.NullOr(IsoDateTime)), pendingRuntimeRequest: Schema.NullOr(OrchestrationV2PendingRuntimeRequestSummary), latestVisibleMessage: Schema.NullOr(OrchestrationV2LatestVisibleMessageSummary), diff --git a/packages/contracts/src/pullRequest.ts b/packages/contracts/src/pullRequest.ts index 5553bb7f176b5..a68f5394b0dd1 100644 --- a/packages/contracts/src/pullRequest.ts +++ b/packages/contracts/src/pullRequest.ts @@ -538,6 +538,7 @@ export type PullRequestInvalidateInput = typeof PullRequestInvalidateInput.Type; export const PullRequestReportStateInput = Schema.Struct({ reference: PullRequestRef, state: PullRequestState, + isDraft: Schema.optional(Schema.Boolean), }); export type PullRequestReportStateInput = typeof PullRequestReportStateInput.Type; diff --git a/packages/contracts/src/scheduledTask.ts b/packages/contracts/src/scheduledTask.ts index 67e2314a8b010..3e1ad5fbdf350 100644 --- a/packages/contracts/src/scheduledTask.ts +++ b/packages/contracts/src/scheduledTask.ts @@ -109,7 +109,7 @@ const WebhookFilterPath = TrimmedNonEmptyString.check( Schema.isPattern(/^(body|headers|query)(\.|$)/), ).annotate({ description: - "Request value to test, in placeholder syntax without braces: headers.x-github-event, body.action, query.source.", + "Request value to test, in placeholder syntax without braces: headers.x-github-event, body.action, query.source. A segment ending in [] fans out over an array, so body.issue.labels[].name tests every label's name.", }); /** @@ -134,7 +134,7 @@ export const ScheduledTaskWebhookFilter = Schema.Union([ Schema.Struct({ path: WebhookFilterPath, op: Schema.Literal("exists") }), ]).annotate({ description: - "Scalars compare as strings; objects and arrays only satisfy 'exists'. Every op but 'exists' fails when the path is missing.", + "Scalars compare as strings; objects and arrays only satisfy 'exists'. Every op but 'exists' fails when the path is missing. A [] wildcard path yields a list: equals, in and startsWith match when any element does, notEquals when no element equals the value, exists when the list is not empty.", }); export type ScheduledTaskWebhookFilter = typeof ScheduledTaskWebhookFilter.Type; export type ScheduledTaskWebhookFilterOp = ScheduledTaskWebhookFilter["op"]; diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts index fb25e1c18692f..25523c2d0fb4a 100644 --- a/packages/contracts/src/settings.test.ts +++ b/packages/contracts/src/settings.test.ts @@ -8,12 +8,15 @@ import { ClientSettingsPatch, ClaudeSettings, DEFAULT_SERVER_SETTINGS, + globalIdleCompaction, + idleCompactionPatch, requiredScopesForProjectMutation, resolveIdleCompaction, resolveProjectProviderInstanceEnabled, resolveProviderInstanceEnabled, ServerSettings, ServerSettingsPatch, + withIdleCompaction, } from "./settings.ts"; const decodeClientSettings = Schema.decodeUnknownSync(ClientSettingsSchema); @@ -138,7 +141,7 @@ describe("custom model settings", () => { describe("resolveIdleCompaction", () => { it("lets a Claude instance override the global default", () => { const settings = decodeServerSettings({ - compactIdleThreads: "off", + idleCompaction: "off", providerInstances: { claude_work: { driver: "claudeAgent", config: { idleCompaction: "onReturn" } }, claude_personal: { driver: "claudeAgent" }, @@ -151,11 +154,38 @@ describe("resolveIdleCompaction", () => { expect(resolveIdleCompaction(settings, ProviderInstanceId.make("claudeAgent"))).toBe("off"); }); - it("keeps the legacy beta switch's behavior", () => { - expect(decodeServerSettings({ compactIdleThreads: true }).compactIdleThreads).toBe( + it("reads the mode from the legacy switch, in either form servers saved it", () => { + expect(globalIdleCompaction(decodeServerSettings({ compactIdleThreads: true }))).toBe( "beforeCacheExpires", ); - expect(decodeServerSettings({}).compactIdleThreads).toBe("onReturn"); + expect(globalIdleCompaction(decodeServerSettings({ compactIdleThreads: "off" }))).toBe("off"); + expect(globalIdleCompaction(decodeServerSettings({}))).toBe("onReturn"); + }); + + it("keeps the legacy switch a boolean, as shipped clients decode it", () => { + const encode = Schema.encodeSync(Schema.toCodecJson(ServerSettings)); + for (const mode of ["onReturn", "beforeCacheExpires", "off"] as const) { + const encoded = encode(withIdleCompaction(DEFAULT_SERVER_SETTINGS, mode)) as { + readonly compactIdleThreads: unknown; + readonly idleCompaction: unknown; + }; + expect(encoded.compactIdleThreads).toBe(mode === "beforeCacheExpires"); + expect(encoded.idleCompaction).toBe(mode); + } + }); + + it("patches the field the server reads", () => { + expect( + idleCompactionPatch({ compactIdleThreads: false, idleCompaction: "onReturn" }, "off"), + ).toEqual({ + idleCompaction: "off", + }); + expect(idleCompactionPatch({ compactIdleThreads: "onReturn" }, "off")).toEqual({ + compactIdleThreads: "off", + }); + expect(idleCompactionPatch({ compactIdleThreads: false }, "beforeCacheExpires")).toEqual({ + compactIdleThreads: true, + }); }); }); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 312f8cc504466..1f400a77af5ce 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -653,17 +653,12 @@ export const IDLE_COMPACTION_MODES = [ export const IdleCompactionMode = Schema.Literals(IDLE_COMPACTION_MODES.map((mode) => mode.value)); export type IdleCompactionMode = typeof IdleCompactionMode.Type; -// `compactIdleThreads` was a beta on/off switch for compacting before the cache expires. -const LegacyCompactIdleThreads = Schema.Boolean.pipe( - Schema.decodeTo( - IdleCompactionMode, - SchemaTransformation.transform({ - decode: (enabled): IdleCompactionMode => (enabled ? "beforeCacheExpires" : "onReturn"), - encode: (mode) => mode === "beforeCacheExpires", - }), - ), -); -const IdleCompactionModeSetting = Schema.Union([IdleCompactionMode, LegacyCompactIdleThreads]); +// `compactIdleThreads` was a beta on/off switch for compacting before the cache +// expires. Shipped clients decode it as a boolean and drop the whole server config +// otherwise, so it stays a boolean on the wire and the mode lives in +// `idleCompaction`. Servers before that split sent the mode here, so a string is +// still read. +const LegacyCompactIdleThreads = Schema.Union([Schema.Boolean, IdleCompactionMode]); const INSTANCE_IDLE_COMPACTION_MODES = [ { value: "default", label: "Use default" }, @@ -1203,9 +1198,10 @@ export const ServerSettings = Schema.Struct({ sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)), ), - compactIdleThreads: IdleCompactionModeSetting.pipe( - Schema.withDecodingDefault(Effect.succeed("onReturn" as const)), + compactIdleThreads: LegacyCompactIdleThreads.pipe( + Schema.withDecodingDefault(Effect.succeed(false)), ), + idleCompaction: Schema.optionalKey(IdleCompactionMode), snoozeLimitedThreads: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), autoResumeLimitedThreads: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), sidebarAutoSettleKeepPullRequests: Schema.Boolean.pipe( @@ -1386,8 +1382,41 @@ export const resolveProjectProviderInstanceEnabled = ( const decodeClaudeSettings = Schema.decodeUnknownOption(ClaudeSettings); +type IdleCompactionSettings = Pick; + +/** The environment-wide idle compaction mode, from whichever field the settings carry. */ +export const globalIdleCompaction = (settings: IdleCompactionSettings): IdleCompactionMode => + settings.idleCompaction ?? + (typeof settings.compactIdleThreads === "string" + ? settings.compactIdleThreads + : settings.compactIdleThreads + ? "beforeCacheExpires" + : "onReturn"); + +/** Sets the mode, and the legacy switch shipped clients still read alongside it. */ +export const withIdleCompaction = ( + settings: S, + mode: IdleCompactionMode, +): S => ({ ...settings, idleCompaction: mode, compactIdleThreads: mode === "beforeCacheExpires" }); + +/** Whether a server reads modes, rather than only the boolean switch from before them. */ +export const readsIdleCompactionModes = (settings: IdleCompactionSettings) => + settings.idleCompaction !== undefined || typeof settings.compactIdleThreads === "string"; + +/** + * The patch that sets `mode` in the form the server's settings show it reads: + * the mode for a server that has `idleCompaction` or kept the mode in the legacy + * field, the boolean switch for a server from before modes. + */ +export const idleCompactionPatch = (settings: IdleCompactionSettings, mode: IdleCompactionMode) => + settings.idleCompaction !== undefined + ? { idleCompaction: mode } + : readsIdleCompactionModes(settings) + ? { compactIdleThreads: mode } + : { compactIdleThreads: mode === "beforeCacheExpires" }; + export const resolveIdleCompaction = ( - settings: Pick, + settings: Pick, instanceId: ProviderInstanceId, ): IdleCompactionMode => { const instance = Object.hasOwn(settings.providerInstances, instanceId) @@ -1397,7 +1426,7 @@ export const resolveIdleCompaction = ( Option.map((config) => config.idleCompaction), Option.getOrElse(() => "default" as const), ); - return override === "default" ? settings.compactIdleThreads : override; + return override === "default" ? globalIdleCompaction(settings) : override; }; export const ServerSettingsOperation = Schema.Literals([ @@ -1541,7 +1570,8 @@ export const ServerSettingsPatch = Schema.Struct({ deviceHosts: Schema.optionalKey(SshDeviceHostConfigs), easDeviceHosts: Schema.optionalKey(EasDeviceHostConfigs), sidebarAutoSettleAfterDays: Schema.optionalKey(Schema.NullOr(SidebarAutoSettleAfterDays)), - compactIdleThreads: Schema.optionalKey(IdleCompactionMode), + compactIdleThreads: Schema.optionalKey(LegacyCompactIdleThreads), + idleCompaction: Schema.optionalKey(IdleCompactionMode), sidebarAutoSettleKeepPullRequests: Schema.optionalKey(Schema.Boolean), sidebarAutoSettleOnMerge: Schema.optionalKey(Schema.Boolean), autoResumeLimitedThreads: Schema.optionalKey(Schema.Boolean), diff --git a/packages/effect-acp/package.json b/packages/effect-acp/package.json index 2b1b703c050e2..0ceaf8e8af958 100644 --- a/packages/effect-acp/package.json +++ b/packages/effect-acp/package.json @@ -37,7 +37,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir .", "generate": "node scripts/generate.ts" }, diff --git a/packages/effect-codex-app-server/package.json b/packages/effect-codex-app-server/package.json index 624d96b91f033..608db8d2ca30f 100644 --- a/packages/effect-codex-app-server/package.json +++ b/packages/effect-codex-app-server/package.json @@ -29,7 +29,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir .", "generate": "node scripts/generate.ts", "probe": "node test/examples/codex-app-server-probe.ts" diff --git a/packages/provider-acp-registry/package.json b/packages/provider-acp-registry/package.json index cd192ba0f1f97..a9c474a9148f8 100644 --- a/packages/provider-acp-registry/package.json +++ b/packages/provider-acp-registry/package.json @@ -29,7 +29,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-acp/package.json b/packages/provider-acp/package.json index 32989728e0c65..f3bbfa0dad385 100644 --- a/packages/provider-acp/package.json +++ b/packages/provider-acp/package.json @@ -45,7 +45,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-core/package.json b/packages/provider-core/package.json index 7003e0a82c9b8..c45de9a9b149f 100644 --- a/packages/provider-core/package.json +++ b/packages/provider-core/package.json @@ -185,7 +185,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-cursor/package.json b/packages/provider-cursor/package.json index 1b4aa9f332839..b139d6ecc8dd8 100644 --- a/packages/provider-cursor/package.json +++ b/packages/provider-cursor/package.json @@ -45,7 +45,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-grok/package.json b/packages/provider-grok/package.json index 1187be63a0344..95ba20214e81a 100644 --- a/packages/provider-grok/package.json +++ b/packages/provider-grok/package.json @@ -25,7 +25,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-muse/package.json b/packages/provider-muse/package.json index a54d78fc585bc..e6e6852c2ba46 100644 --- a/packages/provider-muse/package.json +++ b/packages/provider-muse/package.json @@ -21,7 +21,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-opencode/package.json b/packages/provider-opencode/package.json index a9553691acae7..1517ff3ce941a 100644 --- a/packages/provider-opencode/package.json +++ b/packages/provider-opencode/package.json @@ -45,7 +45,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-opencode/src/server/adapter.test.ts b/packages/provider-opencode/src/server/adapter.test.ts index 92d72ef0b3583..fef2c2ca1f960 100644 --- a/packages/provider-opencode/src/server/adapter.test.ts +++ b/packages/provider-opencode/src/server/adapter.test.ts @@ -840,6 +840,62 @@ describe("OpenCodeAdapterV2", () => { ), ); + it.effect("drops cancelled OpenCode todos so a finished list completes", () => + Effect.gen(function* () { + const nativeEvents = asyncEventStream(); + const nativeSessionId = "native-opencode-todos"; + const harness = yield* makeOpenCodeRuntimeHarness("todos", nativeSessionId, { + event: { + subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { + options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); + return { stream: nativeEvents.stream }; + }, + }, + mcp: { status: async () => ({ data: {} }) }, + session: { + create: async () => ({ + data: { id: nativeSessionId, time: { created: 1, updated: 1 } }, + }), + promptAsync: async () => ({ data: true }), + }, + }); + yield* harness.startTurn(); + const received = yield* harness.runtime.events.pipe( + Stream.takeUntil((event) => event.type === "plan.updated"), + Stream.runCollect, + Effect.forkScoped, + ); + yield* Effect.promise(() => + nativeEvents.push({ + type: "todo.updated", + properties: { + sessionID: nativeSessionId, + todos: [ + { content: "Write the fix", status: "completed", priority: "high" }, + { content: "Try another approach", status: "cancelled", priority: "low" }, + { content: "Add a test", status: "completed", priority: "medium" }, + ], + }, + }), + ); + const plan = (yield* Fiber.join(received)).flatMap((event) => + event.type === "plan.updated" ? [event.plan] : [], + )[0]; + assert.ok(plan?.kind === "todo_list"); + assert.deepEqual( + plan.steps.map((step) => ({ text: step.text, status: step.status })), + [ + { text: "Write the fix", status: "completed" }, + { text: "Add a test", status: "completed" }, + ], + ); + assert.equal(plan.status, "completed"); + }).pipe( + Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer)), + Effect.scoped, + ), + ); + it.effect("presents OpenCode MCP calls without treating remote tools as local edits", () => Effect.gen(function* () { const output = diff --git a/packages/provider-opencode/src/server/adapter.ts b/packages/provider-opencode/src/server/adapter.ts index d8f763f44035e..9a6a6e265b205 100644 --- a/packages/provider-opencode/src/server/adapter.ts +++ b/packages/provider-opencode/src/server/adapter.ts @@ -1665,17 +1665,25 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function driver: OPENCODE_PROVIDER, nativeItemId, }); - const steps: Array = todos.map((todo, index) => ({ - id: `${nativeItemId}:${index + 1}`, - text: todo.content.trim() || `Todo ${index + 1}`, - status: - todo.status === "completed" - ? "completed" - : todo.status === "in_progress" - ? "running" - : "pending", - })); - const completed = steps.length > 0 && steps.every((step) => step.status === "completed"); + // A cancelled todo is not outstanding work, and plan steps have no + // cancelled status, so drop it rather than show it as pending. + const steps: Array = todos.flatMap((todo, index) => + todo.status === "cancelled" + ? [] + : [ + { + id: `${nativeItemId}:${index + 1}`, + text: todo.content.trim() || `Todo ${index + 1}`, + status: + todo.status === "completed" + ? "completed" + : todo.status === "in_progress" + ? "running" + : "pending", + }, + ], + ); + const completed = todos.length > 0 && steps.every((step) => step.status === "completed"); yield* emitProviderEvent({ type: "node.updated", driver: OPENCODE_PROVIDER, diff --git a/packages/provider-pi/package.json b/packages/provider-pi/package.json index 8606fee447362..3eb548606a456 100644 --- a/packages/provider-pi/package.json +++ b/packages/provider-pi/package.json @@ -21,7 +21,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/provider-pi/src/server/adapter.test.ts b/packages/provider-pi/src/server/adapter.test.ts index dcc9023c2b952..9c81bab0abb53 100644 --- a/packages/provider-pi/src/server/adapter.test.ts +++ b/packages/provider-pi/src/server/adapter.test.ts @@ -21,6 +21,7 @@ import { import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Queue from "effect/Queue"; @@ -1801,6 +1802,51 @@ describe("PiAdapterV2", () => { }).pipe(Effect.scoped, Effect.provide(layerTest)), ); + it.effect("natively forks before a running turn whose start entry was not captured yet", () => + Effect.gen(function* () { + const fake = yield* makeFakePi; + const forkFake = yield* makeFakePi; + const forkFile = "/fake/forked.jsonl"; + const { runtime } = yield* openRuntime(fake, "default", THREAD_ID, SESSION_ID, forkFake); + const source = yield* runtime.ensureThread({ + threadId: THREAD_ID, + modelSelection: modelSelection("default"), + runtimePolicy, + }); + // Turn 2 is still in flight, so it has no end to bound the session-tree search. + const turns = uncapturedTurns(source.id, { startedAt: 100, completedAt: 200 }).map((turn) => + turn.ordinal === 2 ? { ...turn, status: "running" as const, completedAt: null } : turn, + ); + forkFake.queueEntries(UNCAPTURED_TURN_TREE); + forkFake.queueState({ sessionFile: forkFile }); + fake.queueState({ sessionFile: forkFile }); + + const result = yield* runtime + .forkThread({ + sourceProviderThread: source, + sourceProviderTurns: turns, + providerTurnId: turns[0]!.id, + targetThreadId: ThreadId.make("fork-target"), + }) + .pipe(Effect.exit); + assert.isTrue( + Exit.isSuccess(result), + `forking turn 1 while turn 2 runs must locate turn 2's prompt in the session tree, got: ${ + Exit.isFailure(result) ? Cause.pretty(result.cause) : "" + }`, + ); + if (!Exit.isSuccess(result)) return; + assert.equal(result.value.nativeThreadRef?.nativeId, forkFile); + + const forkedEntries = forkFake + .allRequests() + .filter((request) => request.type === "fork") + .map((request) => request.entryId); + // Pi keeps only the fork entry's ancestors, so u2 (not steer2) drops all of turn 2. + assert.deepEqual(forkedEntries, ["u2"]); + }).pipe(Effect.scoped, Effect.provide(layerTest)), + ); + it.effect("presents explicitly namespaced MCP extension tools", () => Effect.gen(function* () { const fake = yield* makeFakePi; diff --git a/packages/provider-pi/src/server/adapter.ts b/packages/provider-pi/src/server/adapter.ts index ac2c321323f1e..a2a3caf9267e5 100644 --- a/packages/provider-pi/src/server/adapter.ts +++ b/packages/provider-pi/src/server/adapter.ts @@ -3234,8 +3234,9 @@ function piForkEntry( /** * Pi shares the server clock. An adopted wake can precede its T3 turn, so search from the target - * turn's end through the boundary turn's end. At thread start, use the earlier of thread creation - * and turn start to include adopted wakes while preserving inherited session history. + * turn's end through the boundary turn's end. A boundary turn still running has no end yet, so + * the window stays open through the tree read. At thread start, use the earlier of thread + * creation and turn start to include adopted wakes while preserving inherited session history. */ function piUncapturedForkEntry( listing: PiSessionListing, @@ -3243,12 +3244,12 @@ function piUncapturedForkEntry( providerThreadCreatedAt: DateTime.Utc, ): string | undefined { const { startedAt, completedAt } = boundary.turn; - if (startedAt === null || completedAt === null) return undefined; + if (startedAt === null) return undefined; const from = boundary.target === null ? Math.min(DateTime.toEpochMillis(providerThreadCreatedAt), DateTime.toEpochMillis(startedAt)) : DateTime.toEpochMillis(boundary.target.completedAt ?? startedAt); - const to = DateTime.toEpochMillis(completedAt); + const to = completedAt === null ? Infinity : DateTime.toEpochMillis(completedAt); const targetRef = boundary.target?.nativeTurnRef; const stopAt = targetRef?.strength === "strong" ? targetRef.nativeId : null; if (stopAt !== null && !listing.entries.has(stopAt)) return undefined; diff --git a/packages/provider-testing/package.json b/packages/provider-testing/package.json index d0e5c338cb9ad..0477cbef634e9 100644 --- a/packages/provider-testing/package.json +++ b/packages/provider-testing/package.json @@ -25,7 +25,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit" + "typecheck": "tsc --noEmit" }, "dependencies": { "@effect/platform-node": "catalog:", diff --git a/packages/shared/package.json b/packages/shared/package.json index 31828017d81a3..50e15ca422c54 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -502,10 +502,14 @@ "./markdownMath": { "types": "./src/markdownMath.ts", "import": "./src/markdownMath.ts" + }, + "./cloneUrl": { + "types": "./src/cloneUrl.ts", + "import": "./src/cloneUrl.ts" } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/apps/web/src/lib/addProjectToMachine.test.ts b/packages/shared/src/cloneUrl.test.ts similarity index 94% rename from apps/web/src/lib/addProjectToMachine.test.ts rename to packages/shared/src/cloneUrl.test.ts index 7da234579bbe6..a55192f25e0bb 100644 --- a/apps/web/src/lib/addProjectToMachine.test.ts +++ b/packages/shared/src/cloneUrl.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vite-plus/test"; -import { cloneUrlForProtocol, cloneUrlProtocol } from "./addProjectToMachine"; +import { cloneUrlForProtocol, cloneUrlProtocol } from "./cloneUrl.ts"; describe("cloneUrlForProtocol", () => { it("switches between scp-style SSH and HTTPS", () => { diff --git a/packages/shared/src/cloneUrl.ts b/packages/shared/src/cloneUrl.ts new file mode 100644 index 0000000000000..e4d0fa18beec4 --- /dev/null +++ b/packages/shared/src/cloneUrl.ts @@ -0,0 +1,37 @@ +export type CloneProtocol = "ssh" | "https"; + +const SCP_LIKE_URL = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.+)$/; +const SSH_URL = /^ssh:\/\/(?:[^@/\s]+@)?([^:/\s]+)(:\d+)?\/(.+)$/; +const HTTPS_URL = /^https?:\/\/(?:[^@/\s]+@)?([^:/\s]+)(:\d+)?\/(.+)$/; + +function parseCloneUrl( + url: string, +): { protocol: CloneProtocol; host: string; path: string; customPort: boolean } | null { + const trimmed = url.trim(); + const ssh = SSH_URL.exec(trimmed); + if (ssh) return { protocol: "ssh", host: ssh[1]!, path: ssh[3]!, customPort: !!ssh[2] }; + const https = HTTPS_URL.exec(trimmed); + if (https) return { protocol: "https", host: https[1]!, path: https[3]!, customPort: !!https[2] }; + const scp = SCP_LIKE_URL.exec(trimmed); + if (scp) return { protocol: "ssh", host: scp[1]!, path: scp[2]!, customPort: false }; + return null; +} + +export function cloneUrlProtocol(url: string): CloneProtocol | null { + return parseCloneUrl(url)?.protocol ?? null; +} + +/** + * Rewrites a clone URL to the other protocol, because the target machine may + * only have SSH keys or only an HTTPS credential helper. Returns null when the + * URL cannot be rewritten safely, such as a custom port. + */ +export function cloneUrlForProtocol(url: string, protocol: CloneProtocol): string | null { + const parsed = parseCloneUrl(url); + if (!parsed) return null; + if (parsed.protocol === protocol) return url.trim(); + if (parsed.customPort) return null; + return protocol === "ssh" + ? `git@${parsed.host}:${parsed.path}` + : `https://${parsed.host}/${parsed.path}`; +} diff --git a/packages/shared/src/composerTrigger.test.ts b/packages/shared/src/composerTrigger.test.ts index 677f77c20a421..221ddb5b804cc 100644 --- a/packages/shared/src/composerTrigger.test.ts +++ b/packages/shared/src/composerTrigger.test.ts @@ -41,6 +41,28 @@ describe("detectComposerTrigger", () => { rangeEnd: text.length, }); }); + + it.each([ + "It costs $5", + "budget $9.99", + "only $.99", + "about €20", + "raise $10k", + "$1,000", + "£2e6", + "raise $10k.", + "is it $5?", + "(about $9.99)", + ])("does not open the skill menu for the amount in %j", (text) => + expect(detectComposerTrigger(text, text.length)).toBeNull(), + ); + + it.each(["$", "$1password", "$2fa"])("still detects the skill prefix %j", (text) => { + expect(detectComposerTrigger(text, text.length)).toMatchObject({ + kind: "skill", + query: text.slice(1), + }); + }); }); describe("serializeComposerFileLink", () => { diff --git a/packages/shared/src/composerTrigger.ts b/packages/shared/src/composerTrigger.ts index f8f77ffae3eaa..389a2cc916d64 100644 --- a/packages/shared/src/composerTrigger.ts +++ b/packages/shared/src/composerTrigger.ts @@ -62,6 +62,15 @@ export function isPathWord( return text.slice(wordStart + 1, wordEnd).includes("/"); } +/** + * `$5`, `€9.99` and `$10k` are money, not skills. Matches the amounts the inline skill + * token regexes already refuse to render as skills, plus the punctuation prose puts after + * one (`$10k.`, `($5)`). + */ +export function isCurrencyAmountQuery(query: string): boolean { + return /^\.?[0-9][0-9,._]*(?:[kKmMbBtT]|[eE][0-9]+)?[.,;:!?)]*$/.test(query); +} + export function detectComposerTrigger( text: string, cursorInput: number, @@ -132,6 +141,7 @@ export function detectComposerTrigger( }; const skillPrefix = /^\p{Sc}/u.exec(token); if (skillPrefix) { + if (isCurrencyAmountQuery(token.slice(skillPrefix[0].length))) return null; return { kind: "skill", query: token.slice(skillPrefix[0].length), diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index dafbfce2f9414..8693a8d7af4e3 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -26,6 +26,8 @@ describe("inlineCodeFilePathCandidate", () => { ["origin/main", null], ["z-ai/glm-5.3", null], ["share/man/ls.1", "share/man/ls.1"], + ["src/main.ts/", null], + ["./patches/", "./patches/"], ])("distinguishes file paths from code and hostnames in %s", (source, candidate) => { expect(inlineCodeFilePathCandidate(source)).toBe(candidate); }); @@ -40,13 +42,18 @@ describe("parseMarkdownFileLink", () => { expect(parseMarkdownFileLink(href)?.path).toBe(path); }); - it.each(["https://example.com/docs", "/chat/settings#L3", "TODO:12"])( + it.each(["https://example.com/docs", "/chat/settings#L3", "TODO:12", "patches"])( "does not treat %s as a file", (href) => { expect(parseMarkdownFileLink(href)).toBeNull(); }, ); + it("recognizes a relative folder that ends in a slash", () => { + expect(parseMarkdownFileLink("patches/")).toEqual({ path: "patches/" }); + expect(parseMarkdownFileLink("docs/My%20Folder/")?.path).toBe("docs/My Folder/"); + }); + it("accepts conventional extensionless names with or without a position", () => { expect(parseMarkdownFileLink("Makefile")).toEqual({ path: "Makefile" }); expect(parseMarkdownFileLink("Dockerfile:8")).toEqual({ path: "Dockerfile", line: 8 }); @@ -89,6 +96,13 @@ describe("extractMarkdownLinkHrefs", () => { }); describe("resolveMarkdownFileLinkTarget", () => { + it("resolves a relative folder with a trailing slash against cwd", () => { + expect(resolveMarkdownFileLinkTarget("patches/", "/repo/project")).toBe( + "/repo/project/patches/", + ); + expect(resolveMarkdownFileLinkTarget("patches", "/repo/project")).toBeNull(); + }); + it("does not treat filename line references as external schemes", () => { expect(resolveMarkdownFileLinkTarget("script.ts:10", "/Users/julius/project")).toBe( "/Users/julius/project/script.ts:10", @@ -112,10 +126,15 @@ describe("resolveMarkdownFileLinkTarget", () => { ); }); - it("resolves file uri authorities as windows UNC paths", () => { - expect(resolveMarkdownFileLinkTarget("file://server/share/workspace-image.svg")).toBe( + it("links a network path only inside the project's share", () => { + const cwd = "\\\\server\\share"; + expect(resolveMarkdownFileLinkTarget("file://server/share/workspace-image.svg", cwd)).toBe( "\\\\server\\share\\workspace-image.svg", ); + // Opening these sent the Windows user's NTLM credentials to attacker.test. + for (const href of ["file://attacker.test/s/x.ts", "\\\\attacker.test\\s\\x.ts"]) { + expect(resolveMarkdownFileLinkTarget(href, cwd)).toBeNull(); + } }); it("keeps an encoded final space in the absolute target", () => { diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index 941f26f55c44b..a0195a84749b8 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -1,4 +1,9 @@ -import { isWindowsAbsolutePath, stripSlashPrefixedWindowsDrive } from "./path.ts"; +import { + isNetworkPath, + isWindowsAbsolutePath, + stripSlashPrefixedWindowsDrive, + workspaceRelativeFilePath, +} from "./path.ts"; import { type FilePathPosition, formatFilePathPosition, @@ -9,8 +14,10 @@ import { const RELATIVE_PATH_PREFIX_PATTERN = /^(~\/|\.{1,2}\/)/; +// The final segment is optional so an explicit folder link may end in `/` +// (`patches/`, `docs/My Folder/`). A bare name with no slash still fails. const RELATIVE_FILE_PATH_PATTERN = - /^(?:[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\/)+[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*(?::\d+){0,2}$/; + /^(?:[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\/)+(?:[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*(?::\d+){0,2})?$/; const RELATIVE_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\.[A-Za-z0-9_-]+(?::\d+){0,2}$/; @@ -176,6 +183,9 @@ export function inlineCodeFilePathCandidate(codeText: string): string | null { isWindowsAbsolutePath(candidate); if (!hasExplicitPathShape) { const withoutPosition = candidate.replace(POSITION_SUFFIX_PATTERN, ""); + // Markdown destinations accept `patches/`. Inline code still needs a + // file-shaped final segment, so a trailing slash stays plain code. + if (withoutPosition.endsWith("/")) return null; const firstSegment = withoutPosition.split("/")[0] ?? withoutPosition; if (looksLikeHostname(firstSegment, hasPosition)) return null; const basename = @@ -386,7 +396,13 @@ export function resolveMarkdownFileLinkTarget( if (!target) return null; const pathWithPosition = formatFilePathPosition(target); - if (!isRelativeFilePath(pathWithPosition)) return pathWithPosition; + if (!isRelativeFilePath(pathWithPosition)) { + // Opening a network path makes Windows sign in to its host with the user's + // NTLM credentials, so only the project's own share becomes a file link. + return isNetworkPath(target.path) && workspaceRelativeFilePath(target.path, cwd) === null + ? null + : pathWithPosition; + } if (!baseDir) return null; return resolvePathLinkTarget(pathWithPosition, baseDir); } diff --git a/packages/shared/src/orchestrationV2ThreadError.ts b/packages/shared/src/orchestrationV2ThreadError.ts index c1e92cc133523..740d00881b737 100644 --- a/packages/shared/src/orchestrationV2ThreadError.ts +++ b/packages/shared/src/orchestrationV2ThreadError.ts @@ -5,12 +5,18 @@ import type { } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; +type ErrorTurnItem = Extract; +/** Only error items are read, so callers may pass items stripped of other fields. */ +type FailureScanTurnItem = + | ErrorTurnItem + | { readonly type: Exclude }; + export function latestRootProviderFailure( run: OrchestrationV2Run | null, - turnItems: ReadonlyArray, + turnItems: ReadonlyArray, ): OrchestrationV2ProviderFailure | null { if (run?.status !== "failed") return null; - let latest: Extract | null = null; + let latest: ErrorTurnItem | null = null; for (const item of turnItems) { if ( item.type !== "error" || @@ -41,8 +47,8 @@ export function threadErrorSummary( usageLimitResetAt: currentFailure?.class === "usage_limit" ? (currentFailure.resetAt ?? null) : null, lastError: sessionError ?? failure?.message ?? null, - lastErrorClass: - sessionError !== null && sessionError !== failure?.message ? null : (failure?.class ?? null), + lastErrorClass: currentFailure?.class ?? null, + lastErrorCode: currentFailure?.code ?? null, }; } @@ -77,7 +83,7 @@ export function runRanAfter( export function usageLimitBlockedRun( runs: ReadonlyArray, - turnItems: ReadonlyArray, + turnItems: ReadonlyArray, sessionError: string | null, ): OrchestrationV2Run | null { const executed = latestExecutedRun(runs); @@ -93,7 +99,7 @@ export function usageLimitBlockedRun( */ export function usageLimitRunPresentedAsLatest( runs: ReadonlyArray, - turnItems: ReadonlyArray, + turnItems: ReadonlyArray, sessionError: string | null, ): OrchestrationV2Run | null { const blocked = usageLimitBlockedRun(runs, turnItems, sessionError); diff --git a/packages/shared/src/path.test.ts b/packages/shared/src/path.test.ts index 32cdb8ab2c883..ad7f88c1bd04f 100644 --- a/packages/shared/src/path.test.ts +++ b/packages/shared/src/path.test.ts @@ -43,6 +43,10 @@ describe("workspaceRelativeFilePath", () => { ["/Repo/Project/src/main.ts", "/repo/project", null], ["C:/USERS/mike/t3code/main.ts", "c:/users/MIKE/t3code", "main.ts"], ["/repo/project-two/a.ts", "/repo/project", null], + // regression: #16354 a `..` that leaves the root is a host path, not a workspace path + ["/repo/project/../other/notes.md", "/repo/project", null], + ["C:\\repo\\project\\src\\..\\..\\notes.md", "C:\\repo\\project", null], + ["/repo/project/docs/a/../b.md", "/repo/project", "docs/a/../b.md"], ])("relates %s to %s", (path, workspaceRoot, relativePath) => { expect(workspaceRelativeFilePath(path, workspaceRoot)).toBe(relativePath); }); diff --git a/packages/shared/src/path.ts b/packages/shared/src/path.ts index 62dd11d912963..23a8cf3c6d401 100644 --- a/packages/shared/src/path.ts +++ b/packages/shared/src/path.ts @@ -6,6 +6,15 @@ export function isUncPath(value: string): boolean { return value.startsWith("\\\\"); } +/** + * Windows treats `/` and `\` alike, so any two leading separators name a UNC + * or device path. Touching one makes Windows sign in to that host with the + * user's NTLM credentials, so agent-authored paths must not reach one. + */ +export function isNetworkPath(value: string): boolean { + return /^[\\/]{2}/.test(value); +} + export function isWindowsAbsolutePath(value: string): boolean { return isUncPath(value) || isWindowsDrivePath(value); } @@ -98,7 +107,15 @@ export function workspaceRelativeFilePath( const rootForCompare = caseInsensitive ? normalizedRoot.toLowerCase() : normalizedRoot; if (pathForCompare.replace(/\/+$/, "") === rootForCompare) return "."; if (!pathForCompare.startsWith(`${rootForCompare}/`)) return null; - return normalizedPath.slice(normalizedRoot.length + 1); + const relativePath = normalizedPath.slice(normalizedRoot.length + 1); + // `/repo/../x` starts with the root but escapes it; the server rejects that as a workspace path. + let depth = 0; + for (const segment of relativePath.split("/")) { + if (segment === "..") depth -= 1; + else if (segment.length > 0 && segment !== ".") depth += 1; + if (depth < 0) return null; + } + return relativePath; } export function isAbsolutePath(value: string): boolean { diff --git a/packages/shared/src/serverSettings.test.ts b/packages/shared/src/serverSettings.test.ts index d35cd148605be..51815954ad37d 100644 --- a/packages/shared/src/serverSettings.test.ts +++ b/packages/shared/src/serverSettings.test.ts @@ -52,6 +52,16 @@ describe("fallbackTextGenerationModelSelection", () => { }); describe("serverSettings helpers", () => { + it("translates a shipped client's idle compaction switch into a mode", () => { + const off = applyServerSettingsPatch(DEFAULT_SERVER_SETTINGS, { idleCompaction: "off" }); + expect([off.idleCompaction, off.compactIdleThreads]).toEqual(["off", false]); + // Turning the old switch off keeps "Never" instead of reverting to the default. + const stillOff = applyServerSettingsPatch(off, { compactIdleThreads: false }); + expect(stillOff.idleCompaction).toBe("off"); + const on = applyServerSettingsPatch(off, { compactIdleThreads: true }); + expect([on.idleCompaction, on.compactIdleThreads]).toEqual(["beforeCacheExpires", true]); + }); + it("changes a cleanup rule without replacing the machine's other rules", () => { const enabled = applyServerSettingsPatch(DEFAULT_SERVER_SETTINGS, { storageCleanup: { worktreeAfterDays: 8, worktreeOnMerge: true, logsAfterDays: 30 }, diff --git a/packages/shared/src/serverSettings.ts b/packages/shared/src/serverSettings.ts index 5371cf76e8a79..786126998c96f 100644 --- a/packages/shared/src/serverSettings.ts +++ b/packages/shared/src/serverSettings.ts @@ -4,6 +4,8 @@ import { DEFAULT_TEXT_GENERATION_MODEL, DEFAULT_TEXT_GENERATION_MODEL_BY_PROVIDER, defaultInstanceIdForDriver, + globalIdleCompaction, + type IdleCompactionMode, isProviderAvailable, isUnconfiguredDefaultInstanceEnabled, resolveProjectProviderInstanceEnabled, @@ -18,6 +20,7 @@ import { type ServerProvider, ServerSettings, type ServerSettingsPatch, + withIdleCompaction, } from "@t3tools/contracts"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -359,6 +362,22 @@ function translateLegacyProjectOverridePatch( } as ServerSettingsPatch; } +/** + * A client from before `idleCompaction` patches the old boolean switch. Turning + * it off keeps "Never" rather than reverting to the default. + */ +function patchedIdleCompaction( + current: ServerSettings, + patch: ServerSettingsPatch, +): IdleCompactionMode { + if (patch.idleCompaction !== undefined) return patch.idleCompaction; + const legacy = patch.compactIdleThreads; + if (legacy === undefined) return globalIdleCompaction(current); + if (typeof legacy === "string") return legacy; + if (legacy) return "beforeCacheExpires"; + return globalIdleCompaction(current) === "off" ? "off" : "onReturn"; +} + export function applyServerSettingsPatch( current: ServerSettings, rawPatch: ServerSettingsPatch, @@ -598,7 +617,7 @@ export function applyServerSettingsPatch( normalizedBackgroundActivity, ); const nextWithReplacements = { - ...nextWithReplacementsBase, + ...withIdleCompaction(nextWithReplacementsBase, patchedIdleCompaction(current, patch)), ...deriveLegacyProjectOverrides(nextWithReplacementsBase), backgroundActivity: normalizedBackgroundActivity, automaticGitFetchInterval: resolvedBackgroundActivity.automaticGitFetchInterval, diff --git a/packages/shared/src/sourceControl.test.ts b/packages/shared/src/sourceControl.test.ts index cb45f8c39fb98..23d0c8b4ba81c 100644 --- a/packages/shared/src/sourceControl.test.ts +++ b/packages/shared/src/sourceControl.test.ts @@ -149,6 +149,17 @@ it("names an Azure DevOps repository by its own name, not its project path", () expect(selector).toBe("checkout"); }); +it("decodes an Azure DevOps repository name that the remote URL percent-encodes", () => { + // A remote spells a space as `%20`, and `az repos pr list --repository` reads that literally. + const selector = sourceControlRepositorySelector({ + provider: "azure-devops", + displayName: "contoso/payments%20(eu)/_git/checkout%20(eu)", + owner: "contoso", + name: "checkout%20(eu)", + }); + expect(selector).toBe("checkout (eu)"); +}); + it("keeps a GitLab identity's whole path, because a nested group is part of the name", () => { const selector = sourceControlRepositorySelector({ provider: "gitlab", diff --git a/packages/shared/src/sourceControl.ts b/packages/shared/src/sourceControl.ts index a56d10eb896df..5729d2015014a 100644 --- a/packages/shared/src/sourceControl.ts +++ b/packages/shared/src/sourceControl.ts @@ -278,11 +278,21 @@ export function detectSourceControlProviderFromRemoteUrl( }; } +function decodeUrlPathSegment(segment: string): string { + try { + return decodeURIComponent(segment); + } catch { + return segment; + } +} + /** * Azure DevOps is the exception: `az repos pr list --repository` takes a repository name, and * takes the organisation and project from the checkout it detects — so the recorded * `org/project/_git/repo` path is refused outright and the whole repository reads as - * unavailable. Its name is the last segment, which is what this hands over. + * unavailable. Its name is the last segment, which is what this hands over. The segment comes + * from the remote URL, where a name with a space reads `my%20repo`; `az` looks that up literally + * and answers 404, so it goes out decoded. */ export function sourceControlRepositorySelector( identity: @@ -293,7 +303,8 @@ export function sourceControlRepositorySelector( if (!identity) return null; if (identity.provider === "azure-devops") { const segments = (identity.displayName ?? "").split("/").filter((part) => part !== "_git"); - return identity.name || segments.at(-1) || null; + const name = identity.name || segments.at(-1); + return name ? decodeUrlPathSegment(name) : null; } if (identity.displayName) return identity.displayName; return identity.owner && identity.name ? `${identity.owner}/${identity.name}` : null; diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index fb66d453acdfc..2208b0a1461c0 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -18,6 +18,13 @@ const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; export const CHATGPT_USAGE_URL = "https://chatgpt.com/#settings/Usage"; +export const CHATGPT_USAGE_LIMIT_CODE = "subscription_sharing_usage_limit_exceeded"; +/** A ChatGPT Business admin caps plan use in other apps; only the admin can raise it. */ +export const CHATGPT_WORKSPACE_USAGE_LIMIT_CODE = + "subscription_sharing_workplace_individual_usage_limit_exceeded"; +/** OpenAI's partner guidelines fix this wording; the user sends it to their admin themselves. */ +export const CHATGPT_WORKSPACE_ADMIN_REQUEST = + "I've reached the limit for using my ChatGPT plan in T3 Code. Could you increase our workspace's usage limits so I can continue?\n\nGo to admin.openai.com → Workspace → Usage limits. Thank you!"; export function usesChatGptSharing(provider: ServerProvider | null | undefined): boolean { return provider?.auth.status === "authenticated" && provider.auth.subscriptionSharing === true; diff --git a/packages/source-control-azure-devops/package.json b/packages/source-control-azure-devops/package.json index 456c9b1d01488..4e5f9747c379f 100644 --- a/packages/source-control-azure-devops/package.json +++ b/packages/source-control-azure-devops/package.json @@ -41,7 +41,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-bitbucket/package.json b/packages/source-control-bitbucket/package.json index 7321429132a0b..890c4c34cbe5c 100644 --- a/packages/source-control-bitbucket/package.json +++ b/packages/source-control-bitbucket/package.json @@ -37,7 +37,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-core/package.json b/packages/source-control-core/package.json index 414d5199eae27..d463190918b29 100644 --- a/packages/source-control-core/package.json +++ b/packages/source-control-core/package.json @@ -45,7 +45,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-core/src/server/mergeMessage.test.ts b/packages/source-control-core/src/server/mergeMessage.test.ts index 299bcb3b4451b..0151f09a5c097 100644 --- a/packages/source-control-core/src/server/mergeMessage.test.ts +++ b/packages/source-control-core/src/server/mergeMessage.test.ts @@ -21,6 +21,20 @@ describe("removeAgentCredits", () => { ).toBe("Details about Claude support.\n\n\nCo-authored-by: Alice "); }); + it("removes the footer Claude Code writes today, which links to claude.com", () => { + expect( + removeAgentCredits( + [ + "Details", + "", + "🤖 Generated with [Claude Code](https://claude.com/claude-code)", + "", + "Co-Authored-By: Claude ", + ].join("\n"), + ), + ).toBe("Details"); + }); + it("preserves unknown identities, humans named after agents, and ordinary prose", () => { const message = [ "Co-authored-by: Claude ", diff --git a/packages/source-control-core/src/server/mergeMessage.ts b/packages/source-control-core/src/server/mergeMessage.ts index 8c95ddb441480..159c074260cdd 100644 --- a/packages/source-control-core/src/server/mergeMessage.ts +++ b/packages/source-control-core/src/server/mergeMessage.ts @@ -42,7 +42,7 @@ export function removeAgentCredits(message: string): string { AGENT_EMAILS.has(email) && (email !== "noreply@github.com" || /^github copilot$|^copilot$/i.test(author![1]!)); const footer = text.replace( - /\[([^\]]+)\]\(https:\/\/(?:claude\.ai|www\.anthropic\.com|chatgpt\.com|openai\.com|cursor\.com|github\.com|opencode\.ai|x\.ai|antigravity\.google)(?:\/[^\s)]*)?\)/gi, + /\[([^\]]+)\]\(https:\/\/(?:claude\.ai|claude\.com|www\.anthropic\.com|chatgpt\.com|openai\.com|cursor\.com|github\.com|opencode\.ai|x\.ai|antigravity\.google)(?:\/[^\s)]*)?\)/gi, "$1", ); if (!agentAuthor && !GENERATED_BY_AGENT.test(footer)) return true; diff --git a/packages/source-control-forgejo/package.json b/packages/source-control-forgejo/package.json index 5a2f1a747c900..a33de46a6de8a 100644 --- a/packages/source-control-forgejo/package.json +++ b/packages/source-control-forgejo/package.json @@ -33,7 +33,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-gitcafe/package.json b/packages/source-control-gitcafe/package.json index af7f7740bb9e0..7107cd1e47f0e 100644 --- a/packages/source-control-gitcafe/package.json +++ b/packages/source-control-gitcafe/package.json @@ -37,7 +37,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.test.ts b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.test.ts index d31f73038f201..b9417b319e3b8 100644 --- a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.test.ts +++ b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.test.ts @@ -52,9 +52,11 @@ function fakeGitCafe( const response = route === undefined ? new Response(encodeJson({ type: "https://cafe.sh/errors/not-found" }), { status: 404 }) - : new Response(encodeJson(typeof route === "function" ? route(entry) : route), { - status: options.status ?? 200, - }); + : route instanceof Response + ? route + : new Response(encodeJson(typeof route === "function" ? route(entry) : route), { + status: options.status ?? 200, + }); return Effect.succeed(HttpClientResponse.fromWeb(request, response)); }, ); @@ -104,6 +106,10 @@ const pull = { sourceRepo: null, capabilities: { comment: true, review: true, merge: true, edit: true, moderate: true }, }; +const labels = [ + { id: "label_bug", name: "bug", color: "#d73a4a", description: null }, + { id: "label_docs", name: "docs", color: "#0075ca", description: "Documentation" }, +]; const root = { id: "root", threadId: "root", @@ -439,16 +445,18 @@ describe("GitCafePullRequestProvider", () => { }, ); - it.effect("reads line counts, open merge strategies and requested reviewers", () => { + it.effect("reads line counts, open merge strategies, reviewers and labels", () => { const snapshot = { version: 4, headOid, comparisonBaseOid: baseOid }; const files = (from: number, count: number) => Array.from({ length: count }, (_, index) => ({ path: `f${from + index}.ts` })); let changePage = 0; const server = fakeGitCafe({ - "GET /repos/owner/repo/pulls/7": { ...pull, reviewers: [{ actor }] }, + "GET /repos/owner/repo/pulls/7": pull, + "GET /repos/owner/repo/pulls/7/reviewers": { items: [{ id: "act_one", actor }], next: null }, + "GET /repos/owner/repo/pulls/7/labels": { items: [labels[0]], next: null }, "GET /repos/owner/repo/pulls/7/status": { merge: { - conflicts: "unknown", + conflicts: "none", fastForward: true, strategies: ["merge", "squash", "rebase"], blockers: [{ blockedStrategies: ["squash"] }, {}], @@ -483,7 +491,12 @@ describe("GitCafePullRequestProvider", () => { return Effect.gen(function* () { const provider = yield* GitCafePullRequestProvider.make; const detail = yield* provider.getChangeRequest(target); - assert.deepInclude(detail, { additions: 6, deletions: 3, changedFiles: 3 }); + assert.deepInclude(detail, { + additions: 6, + deletions: 3, + changedFiles: 3, + mergeability: "mergeable", + }); assert.strictEqual( server.sent.filter((request) => request.path.endsWith("/changes"))[1]?.query.get("after"), "cursor-1", @@ -501,6 +514,185 @@ describe("GitCafePullRequestProvider", () => { detail.reviewers.map((reviewer) => reviewer.login), ["alice"], ); + assert.deepStrictEqual(detail.reviewRequestLogins, ["alice"]); + assert.deepStrictEqual(detail.labels, [{ name: "bug", color: "#d73a4a" }]); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("still reads a pull request whose reviewers the token may not see", () => { + const server = fakeGitCafe({ + "GET /repos/owner/repo/pulls/7": { ...pull, headOid: null }, + "GET /repos/owner/repo/pulls/7/reviewers": new Response( + encodeJson({ type: "https://cafe.sh/errors/forbidden" }), + { status: 403 }, + ), + "GET /repos/owner/repo/pulls/7/labels": { items: [labels[0]], next: null }, + "GET /repos/owner/repo/pulls/7/status": { + merge: { conflicts: "unknown", fastForward: null, strategies: [] }, + checks: { pending: 0, failing: 0, total: 0 }, + }, + }); + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const detail = yield* provider.getChangeRequest(target); + assert.deepStrictEqual(detail.reviewers, []); + assert.deepStrictEqual(detail.labels, [{ name: "bug", color: "#d73a4a" }]); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("fails the detail rather than hide reviewers GitCafe could not serve", () => { + const server = fakeGitCafe({ + "GET /repos/owner/repo/pulls/7": { ...pull, headOid: null }, + "GET /repos/owner/repo/pulls/7/reviewers": new Response("", { status: 503 }), + "GET /repos/owner/repo/pulls/7/labels": { items: [], next: null }, + "GET /repos/owner/repo/pulls/7/status": { + merge: { conflicts: "unknown", fastForward: null, strategies: [] }, + checks: { pending: 0, failing: 0, total: 0 }, + }, + }); + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const result = yield* provider.getChangeRequest(target).pipe(Effect.result); + assert.strictEqual(result._tag, "Failure"); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("requests and withdraws reviewers by rewriting the whole list", () => { + const bob = { ...actor, actorId: "act_two", handle: "bob" }; + let requested = [{ id: "act_two", actor: bob }]; + const server = fakeGitCafe({ + "GET /repos/owner/repo/pulls/7": pull, + "GET /repos/owner/repo/pulls/7/reviewer-candidates": { + items: [ + { id: "act_one", actor }, + { id: "act_two", actor: bob }, + { id: "act_bot", actor: { ...actor, actorId: "act_bot", handle: "lint", bot: true } }, + ], + next: null, + }, + "GET /repos/owner/repo/pulls/7/reviewers": () => ({ items: requested, next: null }), + "PUT /repos/owner/repo/pulls/7/reviewers": { version: 5, items: [] }, + }); + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const list = yield* provider.listReviewerCandidates(target); + // The author, alice, cannot review her own pull request. + assert.deepStrictEqual( + list.candidates.map((c) => [c.id, c.login, c.isRequested, c.isBot ?? false]), + [ + ["act_two", "bob", true, false], + ["act_bot", "lint", false, true], + ], + ); + + const set = (id: string, on: boolean) => + provider.setReviewerRequest({ + ...target, + reviewers: [{ id, kind: "user" }], + requested: on, + }); + yield* set("act_bot", true); + assert.deepStrictEqual(server.sent.at(-1)?.body, { + expectedVersion: 4, + actorIds: ["act_two", "act_bot"], + }); + yield* set("act_two", false); + assert.deepStrictEqual(server.sent.at(-1)?.body, { expectedVersion: 4, actorIds: [] }); + + requested = []; + yield* set("act_two", false); + assert.strictEqual(server.writes().length, 2); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("names the missing scopes when the token may not request reviewers", () => { + const server = fakeGitCafe({ + "GET /repos/owner/repo/pulls/7": pull, + "GET /repos/owner/repo/pulls/7/reviewer-candidates": new Response("", { status: 403 }), + "GET /repos/owner/repo/pulls/7/reviewers": { items: [], next: null }, + }); + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const error = yield* Effect.flip(provider.listReviewerCandidates(target)); + assert.match(error.detail, /pr:reviewers:write/u); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("applies and removes labels by name through their ids", () => { + const server = fakeGitCafe({ + "GET /repos/owner/repo/pulls/7": pull, + "GET /repos/owner/repo/labels": { items: labels, next: null }, + "GET /repos/owner/repo/pulls/7/labels": { items: [labels[0]], next: null }, + "PUT /repos/owner/repo/pulls/7/labels": { version: 5, items: [] }, + }); + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const list = yield* provider.listLabelCandidates!(target); + assert.deepStrictEqual( + list.candidates.map((label) => [label.name, label.isApplied]), + [ + ["bug", true], + ["docs", false], + ], + ); + + yield* provider.setLabels!({ ...target, labels: ["docs"], applied: true }); + assert.deepStrictEqual(server.sent.at(-1)?.body, { + expectedVersion: 4, + labelIds: ["label_bug", "label_docs"], + }); + yield* provider.setLabels!({ ...target, labels: ["bug"], applied: false }); + assert.deepStrictEqual(server.sent.at(-1)?.body, { expectedVersion: 4, labelIds: [] }); + + const missing = yield* provider.setLabels!({ + ...target, + labels: ["nope"], + applied: true, + }).pipe(Effect.result); + assert.strictEqual(missing._tag, "Failure"); + assert.strictEqual(server.writes().length, 2); + }).pipe(Effect.provide(server.layer)); + }); + + it.effect("recounts summary lines only when the compared commits move", () => { + const landedBase = "fedcba9876543210fedcba9876543210fedcba98"; + let comparisonBaseOid = baseOid; + let pullReads = 0; + const server = fakeGitCafe({ + // A comment moves the version without moving the compared commits. + "GET /repos/owner/repo/pulls/7": () => ({ ...pull, version: 4 + pullReads++ }), + "GET /repos/owner/repo/pulls/7/changes": () => ({ + version: 4, + headOid, + comparisonBaseOid, + items: [{ path: "a.ts" }, { path: "b.ts" }], + next: null, + }), + "POST /repos/owner/repo/pulls/7/diff-files": (request: SentRequest) => ({ + version: 4, + headOid, + comparisonBaseOid, + items: (request.body as { paths: ReadonlyArray }).paths.map((path) => ({ + path, + additions: comparisonBaseOid === baseOid ? 3 : 1, + deletions: 1, + })), + }), + }); + const hunkReads = () => + server.sent.filter((request) => request.path.endsWith("/diff-files")).length; + return Effect.gen(function* () { + const provider = yield* GitCafePullRequestProvider.make; + const first = yield* provider.getChangeRequestSummary!(target); + const second = yield* provider.getChangeRequestSummary!(target); + assert.deepInclude(first, { additions: 6, deletions: 2, changedFiles: 2 }); + assert.deepInclude(second, { additions: 6, deletions: 2, changedFiles: 2 }); + assert.strictEqual(hunkReads(), 1); + // A landed parent layer moves the comparison base under the same head. + comparisonBaseOid = landedBase; + const third = yield* provider.getChangeRequestSummary!(target); + assert.deepInclude(third, { additions: 2, deletions: 2, changedFiles: 2 }); + assert.strictEqual(hunkReads(), 2); }).pipe(Effect.provide(server.layer)); }); diff --git a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts index 41683de1767d7..a9394f2a86341 100644 --- a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts +++ b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts @@ -31,7 +31,7 @@ const CAPABILITIES: PullRequestCapabilities = { stacks: true, stackActions: true, reactions: true, - labels: false, + labels: true, // Line comments go through GitCafe's review drafts, which arrive with revision fencing. review: { inlineComment: false, @@ -39,7 +39,7 @@ const CAPABILITIES: PullRequestCapabilities = { resolve: true, verdicts: ["approve", "request-changes", "comment"], }, - reviewers: { request: false, listCandidates: false }, + reviewers: { request: true, listCandidates: true }, edit: { changeRequest: true, comment: true }, }; @@ -53,6 +53,7 @@ const DIFF_FILES_BATCH = 64; */ const LINE_STATS_MAX_FILES = DIFF_FILES_BATCH * 4; const MAX_CHANGE_PAGES = 20; +const LINE_STATS_CACHE_SIZE = 256; const chunk =
    (items: ReadonlyArray, size: number): Array> => Array.from({ length: Math.ceil(items.length / size) }, (_, index) => @@ -71,13 +72,16 @@ const mergeStrategyOpen = (status: typeof Status.Type, strategy: string) => const Id = TrimmedNonEmptyString.check(Schema.isPattern(/^[A-Za-z0-9_-]+$/u)); const isId = Schema.is(Id); +const isApiError = Schema.is(GitCafeApi.GitCafeApiError); const Principal = Schema.Struct({ handle: TrimmedNonEmptyString }); const Written = Schema.Struct({ id: Id }); const Versioned = Schema.Struct({ version: NonNegativeInt }); const CommentDetail = Schema.Struct({ id: Id, version: NonNegativeInt }); const Status = Schema.Struct({ merge: Schema.Struct({ - conflicts: Schema.Literals(["unknown", "conflicting"]), + // The API reference lists "unknown" and "conflicting", but git.cafe also sends "none" and + // "pending", so any state is read and only the known ones are mapped. + conflicts: Schema.String, fastForward: Schema.NullOr(Schema.Boolean), strategies: Schema.Array(Schema.String), permitted: Schema.optional(Schema.Boolean), @@ -93,6 +97,10 @@ const Status = Schema.Struct({ total: NonNegativeInt, }), }); +const Listed = Schema.Struct({ + items: Schema.Array(Schema.Struct({ id: TrimmedNonEmptyString })), + next: Schema.NullOr(Schema.String), +}); const FilterOptions = Schema.Struct({ actors: Schema.Array( Schema.Struct({ actorId: TrimmedNonEmptyString, handle: TrimmedNonEmptyString }), @@ -254,6 +262,62 @@ export const make = Effect.gen(function* () { ), ); + const isForbidden = (error: PullRequestProviderError) => + isApiError(error.cause) && error.cause.status === 403; + + /** + * GitCafe offers no way to read a token's scopes, so reviewer controls show + * for anyone who may edit the pull request; a token without the reviewer + * scopes learns which ones it lacks when it uses them. + */ + const explainReviewerScopes = + (operation: string) => + (effect: Effect.Effect) => + effect.pipe( + Effect.catchIf(isForbidden, (error) => + Effect.fail( + failure( + operation, + "This GitCafe token cannot request reviewers. It needs the pr:review:read and pr:reviewers:write scopes.", + error, + ), + ), + ), + ); + + /** + * GitCafe replaces a pull request's whole reviewer or label list, so one toggle reads the + * list, changes it, and writes it back fenced on the pull request's version. The version is + * read first: a change landing between the two reads then fails the fence instead of being + * overwritten. + */ + const toggleListed = Effect.fn("GitCafePullRequestProvider.toggleListed")(function* ( + operation: string, + input: PullRef, + list: { readonly name: "reviewers" | "labels"; readonly key: "actorIds" | "labelIds" }, + ids: ReadonlyArray, + on: boolean, + ) { + const path = yield* pullPath(operation, input); + const pull = yield* readPull(operation, input); + const listed = yield* readPages(operation, input, `${path}/${list.name}`, Listed); + const current = listed.items.map((item) => item.id); + const next = on + ? [...new Set([...current, ...ids])] + : current.filter((id) => !ids.includes(id)); + if (next.length === current.length) return; + yield* read( + operation, + input, + { + method: "PUT", + path: `${path}/${list.name}`, + body: { expectedVersion: pull.version, [list.key]: next }, + }, + Versioned, + ); + }); + /** * GitCafe's stack lists no heads, and they are what a land or restack is fenced on, so each * open layer's pull is read. @@ -354,6 +418,11 @@ export const make = Effect.gen(function* () { return yield* failure(operation, `GitCafe's stack operation ${outcome.state}.`); }); + // GitCafe's pulls carry no line totals, and linked pull requests are summarized every minute, + // so hunk totals are held per compared pair of commits. The file list that names the pair is + // cheap; a retarget or a landed parent layer moves the base without moving the head. + const lineTotals = new Map(); + const readLineStats = Effect.fn("GitCafePullRequestProvider.readLineStats")(function* ( input: PullRef, version: number, @@ -381,21 +450,33 @@ export const make = Effect.gen(function* () { after = batch.next; if (after === null || after === previous || batch.items.length === 0) break; } - const counted = - files.some((file) => file.additions !== undefined) || - files.length > LINE_STATS_MAX_FILES || - revision === undefined - ? files - : (yield* Effect.forEach( + if (revision === undefined) return { changedFiles: 0, additions: 0, deletions: 0 }; + const key = `${input.host} ${input.repository.toLowerCase()} ${input.number} ${revision.baseOid} ${revision.headOid}`; + const held = lineTotals.get(key); + if (held !== undefined) return { changedFiles: files.length, ...held }; + const details = files.some((file) => file.additions !== undefined) + ? Option.some(files) + : files.length > LINE_STATS_MAX_FILES + ? Option.none() + : yield* Effect.forEach( chunk(detailablePaths(files), DIFF_FILES_BATCH), (paths) => readDiffFiles(input, revision, paths), { concurrency: 4 }, - ).pipe(Effect.orElseSucceed(() => [[]]))).flat(); - return { - changedFiles: files.length, + ).pipe( + Effect.map((batches) => batches.flat()), + Effect.option, + ); + // Unread hunks leave the lines unknown rather than zero; the file count still stands. + if (Option.isNone(details)) return { changedFiles: files.length }; + const counted = details.value; + const totals = { additions: counted.reduce((total, file) => total + (file.additions ?? 0), 0), deletions: counted.reduce((total, file) => total + (file.deletions ?? 0), 0), }; + if (lineTotals.size >= LINE_STATS_CACHE_SIZE) + lineTotals.delete(lineTotals.keys().next().value!); + lineTotals.set(key, totals); + return { changedFiles: files.length, ...totals }; }); const merge = Effect.fn("GitCafePullRequestProvider.merge")(function* ( @@ -525,19 +606,33 @@ export const make = Effect.gen(function* () { }; }, ), - getChangeRequestSummary: (input) => - readPull("getChangeRequestSummary", input).pipe( - Effect.map((pull) => ({ - ...Json.toChangeRequest(pull, input), + getChangeRequestSummary: Effect.fn("GitCafePullRequestProvider.getChangeRequestSummary")( + function* (input) { + const pull = yield* readPull("getChangeRequestSummary", input); + const changes = + pull.headOid === null + ? null + : yield* readLineStats(input, pull.version).pipe(Effect.orElseSucceed(() => null)); + // A count that could not be read is absent rather than zero. + const { + additions: _additions, + deletions: _deletions, + ...summary + } = Json.toChangeRequest(pull, input); + return { + ...summary, + ...(changes === null ? {} : changes), closedAt: pull.closedAt, mergedAt: pull.mergedAt, - })), - ), + }; + }, + ), getChangeRequest: Effect.fn("GitCafePullRequestProvider.getChangeRequest")(function* (input) { const pull = yield* readPull("getChangeRequest", input); const path = yield* pullPath("getChangeRequest", input); const base = yield* repositoryPath("getChangeRequest", input); - const [status, checks, changes] = yield* Effect.all( + // A single pull request read carries neither its reviewers nor its labels. + const [status, checks, changes, reviewers, labels] = yield* Effect.all( [ read("getChangeRequest", input, { path: `${path}/status` }, Status), pull.headOid === null @@ -551,12 +646,20 @@ export const make = Effect.gen(function* () { pull.headOid === null ? Effect.succeed(null) : readLineStats(input, pull.version).pipe(Effect.orElseSucceed(() => null)), + // Reviewers need `pr:review:read`, which a contents-only token lacks. + readPages("getChangeRequest", input, `${path}/reviewers`, Json.GitCafeActorEntries).pipe( + Effect.catch((error) => + isForbidden(error) ? Effect.succeed({ items: [], next: null }) : Effect.fail(error), + ), + ), + readPages("getChangeRequest", input, `${path}/labels`, Json.GitCafeLabels), ], - { concurrency: 3 }, + { concurrency: 5 }, ); + const listed = { ...pull, reviewers: reviewers.items, labels: labels.items }; const permissions = Json.toViewerPermissions(pull); return { - ...Json.toChangeRequest(pull, input), + ...Json.toChangeRequest(listed, input), headSha: pull.headOid, body: pull.description ?? "", ...(pull.sourceRepo === null @@ -567,7 +670,7 @@ export const make = Effect.gen(function* () { changedFiles: changes?.changedFiles ?? 0, closedAt: pull.closedAt, mergedAt: pull.mergedAt, - reviewers: Json.toReviewers(pull, input.host), + reviewers: Json.toReviewers(listed, input.host), checks: checks === null ? [] : Json.toChecks(checks), ...(status.checks.total === 0 ? {} @@ -582,7 +685,9 @@ export const make = Effect.gen(function* () { mergeability: status.merge.conflicts === "conflicting" ? ("conflicting" as const) - : ("unknown" as const), + : status.merge.conflicts === "none" + ? ("mergeable" as const) + : ("unknown" as const), baseComparison: status.merge.fastForward === null ? ("unknown" as const) @@ -758,12 +863,80 @@ export const make = Effect.gen(function* () { Written, ); }), - listReviewerCandidates: () => - Effect.fail( - failure("listReviewerCandidates", "GitCafe reviewer requests are not supported."), - ), - setReviewerRequest: () => - Effect.fail(failure("setReviewerRequest", "GitCafe reviewer requests are not supported.")), + listReviewerCandidates: Effect.fn("GitCafePullRequestProvider.listReviewerCandidates")( + function* (input) { + const operation = "listReviewerCandidates"; + const path = yield* pullPath(operation, input); + const [pull, candidates, reviewers] = yield* Effect.all( + [ + readPull(operation, input), + readPages( + operation, + input, + `${path}/reviewer-candidates`, + Json.GitCafeActorEntries, + ).pipe(explainReviewerScopes(operation)), + readPages(operation, input, `${path}/reviewers`, Listed).pipe( + explainReviewerScopes(operation), + ), + ], + { concurrency: 3 }, + ); + return { + candidates: Json.toReviewerCandidates(candidates, { + requested: new Set(reviewers.items.map((reviewer) => reviewer.id)), + authorActorId: pull.author.actorId, + host: input.host, + }), + truncated: candidates.next !== null, + }; + }, + ), + setReviewerRequest: (input) => + toggleListed( + "setReviewerRequest", + input, + { name: "reviewers", key: "actorIds" }, + input.reviewers.map((reviewer) => reviewer.id), + input.requested, + ).pipe(explainReviewerScopes("setReviewerRequest")), + listLabelCandidates: Effect.fn("GitCafePullRequestProvider.listLabelCandidates")( + function* (input) { + const operation = "listLabelCandidates"; + const base = yield* repositoryPath(operation, input); + const [labels, applied] = yield* Effect.all( + [ + readPages(operation, input, `${base}/labels`, Json.GitCafeLabels), + readPages(operation, input, `${base}/pulls/${input.number}/labels`, Listed), + ], + { concurrency: 2 }, + ); + const appliedIds = new Set(applied.items.map((label) => label.id)); + return { + candidates: labels.items.map((label) => ({ + name: label.name, + color: label.color, + description: label.description ?? null, + isApplied: appliedIds.has(label.id), + })), + truncated: labels.next !== null, + }; + }, + ), + setLabels: Effect.fn("GitCafePullRequestProvider.setLabels")(function* (input) { + const base = yield* repositoryPath("setLabels", input); + const labels = yield* readPages("setLabels", input, `${base}/labels`, Json.GitCafeLabels); + const selected = labels.items.filter((label) => input.labels.includes(label.name)); + if (selected.length !== input.labels.length) + return yield* failure("setLabels", "One or more requested labels could not be found."); + yield* toggleListed( + "setLabels", + input, + { name: "labels", key: "labelIds" }, + selected.map((label) => label.id), + input.applied, + ); + }), replyToThread: Effect.fn("GitCafePullRequestProvider.replyToThread")(function* (input) { if (!isId(input.threadId)) return yield* failure("replyToThread", "GitCafe thread ids are opaque slugs."); diff --git a/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts b/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts index 66d1bf3a2b33f..2b848639a04cd 100644 --- a/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts +++ b/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts @@ -12,6 +12,7 @@ import { type PullRequestComment, type PullRequestReaction, type PullRequestReactionContent, + type PullRequestReviewerCandidate, type PullRequestReviewThread, type PullRequestViewerPermissions, } from "@t3tools/contracts"; @@ -29,6 +30,7 @@ const Actor = Schema.Union([ handle: TrimmedNonEmptyString, displayName: Schema.NullOr(Schema.String), avatarUrl: Schema.NullOr(Schema.String), + bot: Schema.optional(Schema.Boolean), }), Schema.Struct({ kind: Schema.Literal("github"), @@ -143,6 +145,24 @@ export const GitCafeCommits = Schema.Struct({ headOid: Schema.String, }); +export const GitCafeLabels = Schema.Struct({ + items: Schema.Array( + Schema.Struct({ + id: TrimmedNonEmptyString, + name: TrimmedNonEmptyString, + color: Schema.NullOr(Schema.String), + description: Schema.optional(Schema.NullOr(Schema.String)), + }), + ), + next: Schema.NullOr(Schema.String), +}); + +/** A pull request's reviewers and its reviewer candidates, each actor under its id. */ +export const GitCafeActorEntries = Schema.Struct({ + items: Schema.Array(Schema.Struct({ id: TrimmedNonEmptyString, actor: Actor })), + next: Schema.NullOr(Schema.String), +}); + export const GitCafeReactions = Schema.Struct({ items: Schema.Array( Schema.Struct({ @@ -249,6 +269,7 @@ function toActor(actor: Actor, host: string): PullRequestActor | null { return { login: actor.kind === "local" ? actor.handle : actor.login, name: actor.kind === "local" ? actor.displayName : null, + ...(actor.kind === "local" && actor.bot === true ? { isBot: true } : {}), // GitCafe serves its own avatars by path, so a relative one is read against the host. avatarUrl: actor.avatarUrl === null ? null : new URL(actor.avatarUrl, `https://${host}`).href, }; @@ -264,6 +285,22 @@ export function toReviewers( }); } +export function toReviewerCandidates( + candidates: typeof GitCafeActorEntries.Type, + input: { + readonly requested: ReadonlySet; + readonly authorActorId: string; + readonly host: string; + }, +): ReadonlyArray { + return candidates.items.flatMap(({ id, actor }) => { + const reviewer = id === input.authorActorId ? null : toActor(actor, input.host); + return reviewer === null + ? [] + : [{ ...reviewer, id, kind: "user" as const, isRequested: input.requested.has(id) }]; + }); +} + function reviewerLogins(actor: Actor, host: string): ReadonlyArray { const reviewer = toActor(actor, host); if (reviewer === null) return []; @@ -293,7 +330,7 @@ export function toChangeRequest( reviewRequestLogins: (pull.reviewers ?? []).flatMap(({ actor }) => reviewerLogins(actor, target.host), ), - labels: pull.labels ?? [], + labels: (pull.labels ?? []).map(({ name, color }) => ({ name, color })), }; } @@ -313,7 +350,8 @@ export function toViewerPermissions(detail: GitCafePullDetail): PullRequestViewe comment: open, resolve: open, verdicts: review ? ["approve", "request-changes", "comment"] : [], - requestReviewers: false, + requestReviewers: edit && detail.state === "open", + labels: edit && detail.state === "open", editChangeRequest: edit, // GitCafe's only branch update moves a whole stack. updateMethods: [], diff --git a/packages/source-control-github/package.json b/packages/source-control-github/package.json index c779194b638a7..7b49441fd0642 100644 --- a/packages/source-control-github/package.json +++ b/packages/source-control-github/package.json @@ -73,7 +73,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir .", "generate": "graphqlgen" }, diff --git a/packages/source-control-gitlab/package.json b/packages/source-control-gitlab/package.json index aafefa73fb4ba..def5c117c558c 100644 --- a/packages/source-control-gitlab/package.json +++ b/packages/source-control-gitlab/package.json @@ -41,7 +41,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/source-control-testing/package.json b/packages/source-control-testing/package.json index 298364a137d63..60c1bbcf43bf6 100644 --- a/packages/source-control-testing/package.json +++ b/packages/source-control-testing/package.json @@ -9,7 +9,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit" + "typecheck": "tsc --noEmit" }, "dependencies": { "@t3tools/contracts": "workspace:*", diff --git a/packages/ssh/package.json b/packages/ssh/package.json index 8c31c3a455802..04b6173bd252a 100644 --- a/packages/ssh/package.json +++ b/packages/ssh/package.json @@ -25,7 +25,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/packages/tailscale/package.json b/packages/tailscale/package.json index b6ab7f4c1034d..e51f077393705 100644 --- a/packages/tailscale/package.json +++ b/packages/tailscale/package.json @@ -9,7 +9,7 @@ } }, "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../../vite.config.ts --dir ." }, "dependencies": { diff --git a/patches/@legendapp__list@3.3.5.patch b/patches/@legendapp__list@3.3.5.patch index e79a4a7ff167c..58f642e725e53 100644 --- a/patches/@legendapp__list@3.3.5.patch +++ b/patches/@legendapp__list@3.3.5.patch @@ -1,5 +1,21 @@ +diff --git a/animated.d.ts b/animated.d.ts +index 14beb98c0bfed9c60611b7d164670798011182ac..9fb0ca056d9dda4d4abeef2bb9bafcf70d0fa243 100644 +--- a/animated.d.ts ++++ b/animated.d.ts +@@ -284,6 +284,11 @@ interface LegendListSpecificProps { + * - true enables both behaviors; false disables both. + */ + maintainVisibleContentPosition?: boolean | MaintainVisibleContentPositionConfig; ++ /** ++ * T3 patch: the scroll offset a change in item or list size should leave, read in the same pass ++ * that repositions the items so the adjustment commits with them. `undefined` keeps the default. ++ */ ++ sizeChangeAnchor?: () => number | undefined; + /** + * Keeps an item visually anchored to the start by adding trailing space when the content below it underflows. + */ diff --git a/keyboard.d.ts b/keyboard.d.ts -index 367945c..0ac7bf8 100644 +index 367945cdfa8a8c260b7a127657a75c016c9ab46f..0ac7bf8b8e9c386058b2374199e58fe8c92d39b3 100644 --- a/keyboard.d.ts +++ b/keyboard.d.ts @@ -279,7 +279,7 @@ type KeyboardChatComposerInsetListRef = { @@ -23,7 +39,7 @@ index 367945c..0ac7bf8 100644 } & React.RefAttributes) => React.ReactElement | null; diff --git a/keyboard.js b/keyboard.js -index 6645bcb..321c4c3 100644 +index 6645bcbb1f77c36c432035eaf8b2d6d924fc8bda..321c4c305129dd3ce3f06127c47d51720a73fb83 100644 --- a/keyboard.js +++ b/keyboard.js @@ -33,19 +33,22 @@ if (typeof __DEV__ !== "undefined" && __DEV__ && !reactNativeKeyboardController. @@ -119,7 +135,7 @@ index 6645bcb..321c4c3 100644 renderScrollComponent: memoList, ...rest diff --git a/keyboard.mjs b/keyboard.mjs -index 87b38b9..111eb24 100644 +index 87b38b9607c2eba6b407c2acfd520633bdb0e7c2..111eb242ba9d9ade1cb912550f7df85cf4f361ee 100644 --- a/keyboard.mjs +++ b/keyboard.mjs @@ -1,7 +1,7 @@ @@ -224,10 +240,22 @@ index 87b38b9..111eb24 100644 renderScrollComponent: memoList, ...rest diff --git a/react-native.d.ts b/react-native.d.ts -index ce1fe00..3ccf6f1 100644 +index ce1fe00001c9e5aee6c6ea8bb2d4757d4586d002..ca6e596aa53327a097a388ffd545a4c6191f24e6 100644 --- a/react-native.d.ts +++ b/react-native.d.ts -@@ -293,6 +293,12 @@ interface LegendListSpecificProps { +@@ -284,6 +284,11 @@ interface LegendListSpecificProps { + * - true enables both behaviors; false disables both. + */ + maintainVisibleContentPosition?: boolean | MaintainVisibleContentPositionConfig; ++ /** ++ * T3 patch: the scroll offset a change in item or list size should leave, read in the same pass ++ * that repositions the items so the adjustment commits with them. `undefined` keeps the default. ++ */ ++ sizeChangeAnchor?: () => number | undefined; + /** + * Keeps an item visually anchored to the start by adding trailing space when the content below it underflows. + */ +@@ -293,6 +298,12 @@ interface LegendListSpecificProps { * The adjustment is also rendered as real content padding so the browser scroll range includes it. */ contentInsetEndAdjustment?: number; @@ -241,7 +269,7 @@ index ce1fe00..3ccf6f1 100644 * Number of columns to render items in. * @default 1 diff --git a/react-native.js b/react-native.js -index b3c5a30..5ac6bbe 100644 +index b3c5a306b293f797a8b338adfca3060c0f6db22b..d7f59b63cd4982dfc9c347732caa77e0974864df 100644 --- a/react-native.js +++ b/react-native.js @@ -717,6 +717,15 @@ function hasActiveInitialScroll(state) { @@ -358,7 +386,7 @@ index b3c5a30..5ac6bbe 100644 scheduleFallbackCheck(100); } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) { finishScrollTo(ctx); -@@ -1560,15 +1590,28 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1560,15 +1590,33 @@ function doMaintainScrollAtEnd(ctx) { } = state; const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold"); const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout); @@ -370,6 +398,11 @@ index b3c5a30..5ac6bbe 100644 state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd; return false; } ++ // T3 patch: a caller anchor already placed this pass's offset; a later scroll would move it. ++ if (state.props.sizeChangeAnchor && !state.mvcpDataChanged && state.props.sizeChangeAnchor() !== void 0) { ++ state.pendingMaintainScrollAtEnd = false; ++ return shouldMaintainScrollAtEnd; ++ } if (shouldMaintainScrollAtEnd) { state.pendingMaintainScrollAtEnd = false; + const maintainAnchoredEndSpace = state.props.anchoredEndSpace; @@ -388,7 +421,7 @@ index b3c5a30..5ac6bbe 100644 } if (!state.maintainingScrollAtEnd) { const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant"; -@@ -1591,9 +1634,18 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1591,9 +1639,18 @@ function doMaintainScrollAtEnd(ctx) { y: 0 }); } else { @@ -410,7 +443,7 @@ index b3c5a30..5ac6bbe 100644 } setTimeout( () => { -@@ -1624,6 +1676,10 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1624,6 +1681,10 @@ function doMaintainScrollAtEnd(ctx) { function requestAdjust(ctx, positionDiff, dataChanged) { const state = ctx.state; if (Math.abs(positionDiff) > 0.1) { @@ -421,7 +454,7 @@ index b3c5a30..5ac6bbe 100644 const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff; const doit = () => { if (needsScrollWorkaround) { -@@ -1728,7 +1784,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { +@@ -1728,7 +1789,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) { return 0; } @@ -432,7 +465,7 @@ index b3c5a30..5ac6bbe 100644 const clampDelta = maxScroll - state.scroll; if (unresolvedAmount < 0) { return Math.max(unresolvedAmount, Math.min(0, clampDelta)); -@@ -1790,7 +1848,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { +@@ -1790,7 +1853,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta); return true; } @@ -441,7 +474,43 @@ index b3c5a30..5ac6bbe 100644 const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll); const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON; if (isAtExpectedNativeClamp) { -@@ -1923,7 +1981,7 @@ function prepareMVCP(ctx, dataChanged) { +@@ -1811,7 +1874,35 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { + } + return false; + } ++// T3 patch: a caller anchor decides where a size change leaves the scroll position. It is read in ++// the same pass that repositions the items, so the adjustment commits with the new positions. + function prepareMVCP(ctx, dataChanged) { ++ // T3 patch: a data change skips the caller anchor, so end maintenance must still run after it. ++ ctx.state.mvcpDataChanged = !!dataChanged; ++ const sizeChangeAnchor = dataChanged ? void 0 : ctx.state.props.sizeChangeAnchor; ++ const restore = prepareDefaultMVCP(ctx, dataChanged); ++ if (!sizeChangeAnchor || ctx.state.scrollingTo !== void 0) { ++ return restore; ++ } ++ return () => { ++ const target = sizeChangeAnchor(); ++ if (target === void 0) { ++ restore == null ? void 0 : restore(); ++ return; ++ } ++ // Content that shrinks under an offset past its new end pulls the native offset back to ++ // that end by itself; only the rest of the way to the target is adjusted. ++ const endOffset = clampScrollOffset(ctx, Number.POSITIVE_INFINITY); ++ if (ctx.state.scroll > endOffset) { ++ ctx.state.scroll = endOffset; ++ } ++ const positionDiff = clampScrollOffset(ctx, target) - ctx.state.scroll; ++ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) { ++ requestAdjust(ctx, positionDiff, false); ++ } ++ }; ++} ++function prepareDefaultMVCP(ctx, dataChanged) { + const state = ctx.state; + const { idsInView, positions, props } = state; + const { +@@ -1923,7 +2014,7 @@ function prepareMVCP(ctx, dataChanged) { if (diff > 0) { diff = Math.max(0, totalSize - state.scroll - state.scrollLength); } else { @@ -450,7 +519,7 @@ index b3c5a30..5ac6bbe 100644 state.scroll = maxScroll; state.scrollPending = maxScroll; diff = 0; -@@ -2320,8 +2378,121 @@ function scrollToIndex(ctx, { +@@ -2320,8 +2411,121 @@ function scrollToIndex(ctx, { } // src/core/initialScroll.ts @@ -572,7 +641,7 @@ index b3c5a30..5ac6bbe 100644 const requestedIndex = target.index; const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0; const itemSize = getItemSizeAtIndex(ctx, index); -@@ -2747,7 +2918,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { +@@ -2747,7 +2951,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { return; } if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) { @@ -583,7 +652,7 @@ index b3c5a30..5ac6bbe 100644 if (!shouldKeepEndTargetAlive) { if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) { clearPendingInitialScrollFooterLayout(ctx, { -@@ -4672,7 +4845,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { +@@ -4672,7 +4878,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap); contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props); isReady = !hasUnknownTailSize; @@ -593,7 +662,7 @@ index b3c5a30..5ac6bbe 100644 } else if (anchorIndex >= 0) { isReady = false; } -@@ -4692,6 +4866,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { +@@ -4692,6 +4899,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { updateScroll(ctx, state.scroll, true, { markHasScrolled: false }); } (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize }); @@ -606,15 +675,20 @@ index b3c5a30..5ac6bbe 100644 } return nextSize; } -@@ -5715,6 +5895,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ +@@ -5715,8 +5928,11 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ horizontal }) { const ctx = useStateContext(); + const SizeComponent = ctx.state.props.sizeComponentInternal; const columnWrapperStyle = ctx.columnWrapperStyle; - const animSize = useValue$("totalSize"); +- const animSize = useValue$("totalSize"); ++ // T3 patch: on the new architecture an animated value commits on its own, ahead of the item ++ // positions and their scroll compensation; a rendered size commits with them. ++ const animSize = IsNewArchitecture ? useArr$(["totalSize"])[0] || 0 : useValue$("totalSize"); const [readyToRender, numColumns, otherAxisSize = 0] = useArr$(["readyToRender", "numColumns", "otherAxisSize"]); -@@ -5725,6 +5906,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ + const isVisible = useFreshDataTransitionVisibility(!!readyToRender, freshDataTransitionEpoch); + const style = horizontal ? { +@@ -5725,6 +5941,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ opacity: isVisible ? 1 : 0, width: animSize } : { height: animSize, minWidth: otherAxisSize, opacity: isVisible ? 1 : 0 }; @@ -628,7 +702,7 @@ index b3c5a30..5ac6bbe 100644 if (columnWrapperStyle) { const { columnGap, rowGap, gap } = columnWrapperStyle; const gapX = columnGap || gap || 0; -@@ -5745,7 +5933,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ +@@ -5745,7 +5968,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ } } } @@ -638,7 +712,7 @@ index b3c5a30..5ac6bbe 100644 }); var Containers = typedMemo(function Containers2({ freshDataTransitionEpoch, -@@ -5896,7 +6085,12 @@ var StyleSheet = ReactNative.StyleSheet; +@@ -5896,7 +6120,12 @@ var StyleSheet = ReactNative.StyleSheet; // src/components/ListComponent.tsx var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) { @@ -651,7 +725,7 @@ index b3c5a30..5ac6bbe 100644 if (alignItemsAtEndPadding <= 0) { return null; } -@@ -5929,8 +6123,12 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -5929,8 +6158,12 @@ var ListComponent = typedMemo(function ListComponent2({ refScrollView, renderScrollComponent, onLayoutFooter, @@ -664,7 +738,7 @@ index b3c5a30..5ac6bbe 100644 scrollAdjustHandler, snapToIndices, stickyHeaderConfig, -@@ -6001,7 +6199,17 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -6001,7 +6234,17 @@ var ListComponent = typedMemo(function ListComponent2({ SnapOrScroll, { ...rest, @@ -683,7 +757,7 @@ index b3c5a30..5ac6bbe 100644 contentContainerStyle: [ horizontal ? { height: "100%" } : {}, contentContainerStyle, -@@ -6010,7 +6218,10 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -6010,7 +6253,10 @@ var ListComponent = typedMemo(function ListComponent2({ ], contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0, horizontal, @@ -695,7 +769,17 @@ index b3c5a30..5ac6bbe 100644 onLayout, onScroll: onScroll2, ref: refScrollView, -@@ -6751,7 +6962,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { +@@ -6267,7 +6513,8 @@ function handleLayout(ctx, layoutParam, setCanRender) { + if (scrollLength > 0) { + doInitialAllocateContainers(ctx); + } +- if (needsCalculate) { ++ // T3 patch: a shorter list keeps a caller anchor too, such as the end. ++ if (needsCalculate || state.props.sizeChangeAnchor) { + calculateItemsInView(ctx, { doMVCP: true }); + } + if (didChange || otherAxisSize !== prevOtherAxisSize) { +@@ -6751,7 +6998,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { endBuffered: state.endBuffered, getAverageItemSizes: () => getAverageItemSizes(state), indexByKey: (key) => state.indexByKey.get(key), @@ -704,7 +788,7 @@ index b3c5a30..5ac6bbe 100644 isAtStart: peek$(ctx, "isAtStart"), isEndReached: state.isEndReached, isNearEnd: peek$(ctx, "isNearEnd"), -@@ -7075,6 +7286,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7075,6 +7322,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded dataVersion, drawDistance = 250, contentInsetEndAdjustment, @@ -712,7 +796,15 @@ index b3c5a30..5ac6bbe 100644 estimatedItemSize = 100, estimatedListSize, extraData, -@@ -7132,10 +7344,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7096,6 +7344,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded + maintainScrollAtEnd = false, + maintainScrollAtEndThreshold = 0.1, + maintainVisibleContentPosition: maintainVisibleContentPositionProp, ++ sizeChangeAnchor, + numColumns: numColumnsProp = 1, + overrideItemLayout, + onEndReached, +@@ -7132,10 +7381,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded const animatedPropsInternal = props.animatedPropsInternal; const anchoredEndSpaceOwner = (_a3 = props.anchoredEndSpaceOwnerInternal) != null ? _a3 : "list"; const positionComponentInternal = props.positionComponentInternal; @@ -725,7 +817,7 @@ index b3c5a30..5ac6bbe 100644 stickyPositionComponentInternal: _stickyPositionComponentInternal, ...restProps } = rest; -@@ -7200,7 +7414,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7200,7 +7451,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded const combinedRef = useCombinedRef(refScroller, refScrollView); const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString()); const stickyHeaderIndices = stickyHeaderIndicesProp; @@ -734,7 +826,7 @@ index b3c5a30..5ac6bbe 100644 const previousContentInsetEndAdjustmentRef = React2.useRef(contentInsetEndAdjustmentResolved); const alwaysRenderIndices = React2.useMemo(() => { const indices = getAlwaysRenderIndices(alwaysRender, dataProp, keyExtractor, anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex); -@@ -7341,6 +7555,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7341,6 +7592,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded contentContainerAlignItems: contentContainerStyle.alignItems, contentInset, contentInsetEndAdjustment: contentInsetEndAdjustmentResolved, @@ -742,7 +834,15 @@ index b3c5a30..5ac6bbe 100644 data: dataProp, dataKey, dataVersion, -@@ -7372,6 +7587,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7354,6 +7606,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded + maintainScrollAtEnd: maintainScrollAtEndConfig, + maintainScrollAtEndThreshold, + maintainVisibleContentPosition: maintainVisibleContentPositionConfig, ++ sizeChangeAnchor, + numColumns: numColumnsProp, + onEndReached, + onEndReachedThreshold, +@@ -7372,6 +7625,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded renderItem, rtl, snapToIndices, @@ -750,7 +850,7 @@ index b3c5a30..5ac6bbe 100644 stickyHeaderIndicesArr: stickyHeaderIndices != null ? stickyHeaderIndices : [], stickyHeaderIndicesSet: React2.useMemo(() => new Set(stickyHeaderIndices != null ? stickyHeaderIndices : []), [stickyHeaderIndices == null ? void 0 : stickyHeaderIndices.join(",")]), stickyPositionComponentInternal, -@@ -7423,6 +7639,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7423,6 +7677,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded return void 0; } const resolvedOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(ctx, initialScroll); @@ -764,7 +864,7 @@ index b3c5a30..5ac6bbe 100644 return usesBootstrapInitialScroll && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap" && Platform.OS === "web" ? void 0 : resolvedOffset; }, [usesBootstrapInitialScroll]); React2.useLayoutEffect(() => { -@@ -7547,6 +7770,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7547,6 +7808,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded [ dataKey, dataVersion, @@ -772,7 +872,7 @@ index b3c5a30..5ac6bbe 100644 memoizedLastItemKeys.join(","), numColumnsProp, nextScrollAxisGap, -@@ -7643,6 +7867,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7643,6 +7905,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded () => ({ getRenderedItem: (key) => getRenderedItem(ctx, key), onMomentumScrollEnd: (event) => { @@ -780,7 +880,7 @@ index b3c5a30..5ac6bbe 100644 checkFinishedScrollFallback(ctx); if (state.props.onMomentumScrollEnd) { state.props.onMomentumScrollEnd(event); -@@ -7651,6 +7876,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7651,6 +7914,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded onScroll: (event) => onScroll(ctx, event), onScrollBeginDrag: (event) => { var _a4, _b2; @@ -789,7 +889,7 @@ index b3c5a30..5ac6bbe 100644 prepareReachedEdgeForNextUserScroll(ctx); (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event); }, -@@ -7676,11 +7903,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7676,11 +7941,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded ListFooterComponent, ListFooterComponentStyle, ListHeaderComponent, @@ -809,7 +909,7 @@ index b3c5a30..5ac6bbe 100644 recycleItems, refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2__namespace.cloneElement(refreshControlElement, { diff --git a/react-native.mjs b/react-native.mjs -index 40e87cd..93aac74 100644 +index 40e87cda8c9bc79a889e5542f29af429a24b24d4..c2ca39bab6ea1ebaaed25190a01e8cf2e395ece9 100644 --- a/react-native.mjs +++ b/react-native.mjs @@ -696,6 +696,15 @@ function hasActiveInitialScroll(state) { @@ -926,7 +1026,7 @@ index 40e87cd..93aac74 100644 scheduleFallbackCheck(100); } else if (shouldFinishZeroTarget || shouldFinishAfterObservedScroll || canFinishInitialScrollWithoutNativeProgress || canFinishAfterSilentNativeDispatch || numChecks > maxChecks) { finishScrollTo(ctx); -@@ -1539,15 +1569,28 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1539,15 +1569,33 @@ function doMaintainScrollAtEnd(ctx) { } = state; const isWithinMaintainScrollAtEndThreshold = peek$(ctx, "isWithinMaintainScrollAtEndThreshold"); const shouldMaintainScrollAtEnd = !!(isWithinMaintainScrollAtEndThreshold && maintainScrollAtEnd && didContainersLayout); @@ -938,6 +1038,11 @@ index 40e87cd..93aac74 100644 state.pendingMaintainScrollAtEnd = shouldMaintainScrollAtEnd; return false; } ++ // T3 patch: a caller anchor already placed this pass's offset; a later scroll would move it. ++ if (state.props.sizeChangeAnchor && !state.mvcpDataChanged && state.props.sizeChangeAnchor() !== void 0) { ++ state.pendingMaintainScrollAtEnd = false; ++ return shouldMaintainScrollAtEnd; ++ } if (shouldMaintainScrollAtEnd) { state.pendingMaintainScrollAtEnd = false; + const maintainAnchoredEndSpace = state.props.anchoredEndSpace; @@ -956,7 +1061,7 @@ index 40e87cd..93aac74 100644 } if (!state.maintainingScrollAtEnd) { const pendingState = maintainScrollAtEnd.animated ? "pending-animated" : "pending-instant"; -@@ -1570,9 +1613,18 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1570,9 +1618,18 @@ function doMaintainScrollAtEnd(ctx) { y: 0 }); } else { @@ -978,7 +1083,7 @@ index 40e87cd..93aac74 100644 } setTimeout( () => { -@@ -1603,6 +1655,10 @@ function doMaintainScrollAtEnd(ctx) { +@@ -1603,6 +1660,10 @@ function doMaintainScrollAtEnd(ctx) { function requestAdjust(ctx, positionDiff, dataChanged) { const state = ctx.state; if (Math.abs(positionDiff) > 0.1) { @@ -989,7 +1094,7 @@ index 40e87cd..93aac74 100644 const needsScrollWorkaround = Platform.OS === "android" && !IsNewArchitecture && dataChanged && state.scroll <= positionDiff; const doit = () => { if (needsScrollWorkaround) { -@@ -1707,7 +1763,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { +@@ -1707,7 +1768,9 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) { if (Math.abs(unresolvedAmount) <= MVCP_POSITION_EPSILON) { return 0; } @@ -1000,7 +1105,7 @@ index 40e87cd..93aac74 100644 const clampDelta = maxScroll - state.scroll; if (unresolvedAmount < 0) { return Math.max(unresolvedAmount, Math.min(0, clampDelta)); -@@ -1769,7 +1827,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { +@@ -1769,7 +1832,7 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { settlePendingNativeMVCPAdjust(ctx, remainingAfterManual, nativeDelta); return true; } @@ -1009,7 +1114,43 @@ index 40e87cd..93aac74 100644 const distanceToClamp = Math.abs(newScroll - expectedNativeClampScroll); const isAtExpectedNativeClamp = distanceToClamp <= NATIVE_END_CLAMP_EPSILON; if (isAtExpectedNativeClamp) { -@@ -1902,7 +1960,7 @@ function prepareMVCP(ctx, dataChanged) { +@@ -1790,7 +1853,35 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) { + } + return false; + } ++// T3 patch: a caller anchor decides where a size change leaves the scroll position. It is read in ++// the same pass that repositions the items, so the adjustment commits with the new positions. + function prepareMVCP(ctx, dataChanged) { ++ // T3 patch: a data change skips the caller anchor, so end maintenance must still run after it. ++ ctx.state.mvcpDataChanged = !!dataChanged; ++ const sizeChangeAnchor = dataChanged ? void 0 : ctx.state.props.sizeChangeAnchor; ++ const restore = prepareDefaultMVCP(ctx, dataChanged); ++ if (!sizeChangeAnchor || ctx.state.scrollingTo !== void 0) { ++ return restore; ++ } ++ return () => { ++ const target = sizeChangeAnchor(); ++ if (target === void 0) { ++ restore == null ? void 0 : restore(); ++ return; ++ } ++ // Content that shrinks under an offset past its new end pulls the native offset back to ++ // that end by itself; only the rest of the way to the target is adjusted. ++ const endOffset = clampScrollOffset(ctx, Number.POSITIVE_INFINITY); ++ if (ctx.state.scroll > endOffset) { ++ ctx.state.scroll = endOffset; ++ } ++ const positionDiff = clampScrollOffset(ctx, target) - ctx.state.scroll; ++ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) { ++ requestAdjust(ctx, positionDiff, false); ++ } ++ }; ++} ++function prepareDefaultMVCP(ctx, dataChanged) { + const state = ctx.state; + const { idsInView, positions, props } = state; + const { +@@ -1902,7 +1993,7 @@ function prepareMVCP(ctx, dataChanged) { if (diff > 0) { diff = Math.max(0, totalSize - state.scroll - state.scrollLength); } else { @@ -1018,7 +1159,7 @@ index 40e87cd..93aac74 100644 state.scroll = maxScroll; state.scrollPending = maxScroll; diff = 0; -@@ -2299,8 +2357,121 @@ function scrollToIndex(ctx, { +@@ -2299,8 +2390,121 @@ function scrollToIndex(ctx, { } // src/core/initialScroll.ts @@ -1140,7 +1281,7 @@ index 40e87cd..93aac74 100644 const requestedIndex = target.index; const index = requestedIndex !== void 0 ? clampScrollIndex(requestedIndex, ctx.state.props.data.length) : void 0; const itemSize = getItemSizeAtIndex(ctx, index); -@@ -2726,7 +2897,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { +@@ -2726,7 +2930,9 @@ function clearFinishedBootstrapInitialScrollTargetIfMovedAway(ctx) { return; } if (didFinishedInitialScrollMoveAwayFromTarget(ctx, initialScroll)) { @@ -1151,7 +1292,7 @@ index 40e87cd..93aac74 100644 if (!shouldKeepEndTargetAlive) { if (shouldPreserveInitialScrollForFooterLayout(initialScroll)) { clearPendingInitialScrollFooterLayout(ctx, { -@@ -4651,7 +4824,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { +@@ -4651,7 +4857,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { contentBelowAnchor = Math.max(0, contentBelowAnchor - ctx.scrollAxisGap); contentBelowAnchor += (ctx.values.get("footerSize") || 0) + getStylePaddingEnd(state.props); isReady = !hasUnknownTailSize; @@ -1161,7 +1302,7 @@ index 40e87cd..93aac74 100644 } else if (anchorIndex >= 0) { isReady = false; } -@@ -4671,6 +4845,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { +@@ -4671,6 +4878,12 @@ function maybeUpdateAnchoredEndSpace(ctx) { updateScroll(ctx, state.scroll, true, { markHasScrolled: false }); } (_b = anchoredEndSpace == null ? void 0 : anchoredEndSpace.onReady) == null ? void 0 : _b.call(anchoredEndSpace, { anchorIndex: nextAnchorIndex, anchorKey: nextAnchorKey, size: nextSize }); @@ -1174,15 +1315,20 @@ index 40e87cd..93aac74 100644 } return nextSize; } -@@ -5694,6 +5874,7 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ +@@ -5694,8 +5907,11 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ horizontal }) { const ctx = useStateContext(); + const SizeComponent = ctx.state.props.sizeComponentInternal; const columnWrapperStyle = ctx.columnWrapperStyle; - const animSize = useValue$("totalSize"); +- const animSize = useValue$("totalSize"); ++ // T3 patch: on the new architecture an animated value commits on its own, ahead of the item ++ // positions and their scroll compensation; a rendered size commits with them. ++ const animSize = IsNewArchitecture ? useArr$(["totalSize"])[0] || 0 : useValue$("totalSize"); const [readyToRender, numColumns, otherAxisSize = 0] = useArr$(["readyToRender", "numColumns", "otherAxisSize"]); -@@ -5704,6 +5885,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ + const isVisible = useFreshDataTransitionVisibility(!!readyToRender, freshDataTransitionEpoch); + const style = horizontal ? { +@@ -5704,6 +5920,13 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ opacity: isVisible ? 1 : 0, width: animSize } : { height: animSize, minWidth: otherAxisSize, opacity: isVisible ? 1 : 0 }; @@ -1196,7 +1342,7 @@ index 40e87cd..93aac74 100644 if (columnWrapperStyle) { const { columnGap, rowGap, gap } = columnWrapperStyle; const gapX = columnGap || gap || 0; -@@ -5724,7 +5912,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ +@@ -5724,7 +5947,8 @@ var ContainersLayer = typedMemo(function ContainersLayer2({ } } } @@ -1206,7 +1352,7 @@ index 40e87cd..93aac74 100644 }); var Containers = typedMemo(function Containers2({ freshDataTransitionEpoch, -@@ -5875,7 +6064,12 @@ var StyleSheet = StyleSheet$1; +@@ -5875,7 +6099,12 @@ var StyleSheet = StyleSheet$1; // src/components/ListComponent.tsx var AlignItemsAtEndSpacer = typedMemo(function AlignItemsAtEndSpacer2({ horizontal }) { @@ -1219,7 +1365,7 @@ index 40e87cd..93aac74 100644 if (alignItemsAtEndPadding <= 0) { return null; } -@@ -5908,8 +6102,12 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -5908,8 +6137,12 @@ var ListComponent = typedMemo(function ListComponent2({ refScrollView, renderScrollComponent, onLayoutFooter, @@ -1232,7 +1378,7 @@ index 40e87cd..93aac74 100644 scrollAdjustHandler, snapToIndices, stickyHeaderConfig, -@@ -5980,7 +6178,17 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -5980,7 +6213,17 @@ var ListComponent = typedMemo(function ListComponent2({ SnapOrScroll, { ...rest, @@ -1251,7 +1397,7 @@ index 40e87cd..93aac74 100644 contentContainerStyle: [ horizontal ? { height: "100%" } : {}, contentContainerStyle, -@@ -5989,7 +6197,10 @@ var ListComponent = typedMemo(function ListComponent2({ +@@ -5989,7 +6232,10 @@ var ListComponent = typedMemo(function ListComponent2({ ], contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0, horizontal, @@ -1263,7 +1409,17 @@ index 40e87cd..93aac74 100644 onLayout, onScroll: onScroll2, ref: refScrollView, -@@ -6730,7 +6941,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { +@@ -6246,7 +6492,8 @@ function handleLayout(ctx, layoutParam, setCanRender) { + if (scrollLength > 0) { + doInitialAllocateContainers(ctx); + } +- if (needsCalculate) { ++ // T3 patch: a shorter list keeps a caller anchor too, such as the end. ++ if (needsCalculate || state.props.sizeChangeAnchor) { + calculateItemsInView(ctx, { doMVCP: true }); + } + if (didChange || otherAxisSize !== prevOtherAxisSize) { +@@ -6730,7 +6977,7 @@ function createImperativeHandle(ctx, scheduleImperativeScrollCommit) { endBuffered: state.endBuffered, getAverageItemSizes: () => getAverageItemSizes(state), indexByKey: (key) => state.indexByKey.get(key), @@ -1272,7 +1428,7 @@ index 40e87cd..93aac74 100644 isAtStart: peek$(ctx, "isAtStart"), isEndReached: state.isEndReached, isNearEnd: peek$(ctx, "isNearEnd"), -@@ -7054,6 +7265,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7054,6 +7301,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded dataVersion, drawDistance = 250, contentInsetEndAdjustment, @@ -1280,7 +1436,15 @@ index 40e87cd..93aac74 100644 estimatedItemSize = 100, estimatedListSize, extraData, -@@ -7111,10 +7323,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7075,6 +7323,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded + maintainScrollAtEnd = false, + maintainScrollAtEndThreshold = 0.1, + maintainVisibleContentPosition: maintainVisibleContentPositionProp, ++ sizeChangeAnchor, + numColumns: numColumnsProp = 1, + overrideItemLayout, + onEndReached, +@@ -7111,10 +7360,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded const animatedPropsInternal = props.animatedPropsInternal; const anchoredEndSpaceOwner = (_a3 = props.anchoredEndSpaceOwnerInternal) != null ? _a3 : "list"; const positionComponentInternal = props.positionComponentInternal; @@ -1293,7 +1457,7 @@ index 40e87cd..93aac74 100644 stickyPositionComponentInternal: _stickyPositionComponentInternal, ...restProps } = rest; -@@ -7179,7 +7393,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7179,7 +7430,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded const combinedRef = useCombinedRef(refScroller, refScrollView); const keyExtractor = keyExtractorProp != null ? keyExtractorProp : ((_item, index) => index.toString()); const stickyHeaderIndices = stickyHeaderIndicesProp; @@ -1302,7 +1466,7 @@ index 40e87cd..93aac74 100644 const previousContentInsetEndAdjustmentRef = useRef(contentInsetEndAdjustmentResolved); const alwaysRenderIndices = useMemo(() => { const indices = getAlwaysRenderIndices(alwaysRender, dataProp, keyExtractor, anchoredEndSpace == null ? void 0 : anchoredEndSpace.anchorIndex); -@@ -7320,6 +7534,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7320,6 +7571,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded contentContainerAlignItems: contentContainerStyle.alignItems, contentInset, contentInsetEndAdjustment: contentInsetEndAdjustmentResolved, @@ -1310,7 +1474,15 @@ index 40e87cd..93aac74 100644 data: dataProp, dataKey, dataVersion, -@@ -7351,6 +7566,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7333,6 +7585,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded + maintainScrollAtEnd: maintainScrollAtEndConfig, + maintainScrollAtEndThreshold, + maintainVisibleContentPosition: maintainVisibleContentPositionConfig, ++ sizeChangeAnchor, + numColumns: numColumnsProp, + onEndReached, + onEndReachedThreshold, +@@ -7351,6 +7604,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded renderItem, rtl, snapToIndices, @@ -1318,7 +1490,7 @@ index 40e87cd..93aac74 100644 stickyHeaderIndicesArr: stickyHeaderIndices != null ? stickyHeaderIndices : [], stickyHeaderIndicesSet: useMemo(() => new Set(stickyHeaderIndices != null ? stickyHeaderIndices : []), [stickyHeaderIndices == null ? void 0 : stickyHeaderIndices.join(",")]), stickyPositionComponentInternal, -@@ -7402,6 +7618,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7402,6 +7656,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded return void 0; } const resolvedOffset = (_a4 = initialScroll.contentOffset) != null ? _a4 : resolveInitialScrollOffset(ctx, initialScroll); @@ -1332,7 +1504,7 @@ index 40e87cd..93aac74 100644 return usesBootstrapInitialScroll && ((_b2 = state.initialScrollSession) == null ? void 0 : _b2.kind) === "bootstrap" && Platform.OS === "web" ? void 0 : resolvedOffset; }, [usesBootstrapInitialScroll]); useLayoutEffect(() => { -@@ -7526,6 +7749,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7526,6 +7787,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded [ dataKey, dataVersion, @@ -1340,7 +1512,7 @@ index 40e87cd..93aac74 100644 memoizedLastItemKeys.join(","), numColumnsProp, nextScrollAxisGap, -@@ -7622,6 +7846,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7622,6 +7884,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded () => ({ getRenderedItem: (key) => getRenderedItem(ctx, key), onMomentumScrollEnd: (event) => { @@ -1348,7 +1520,7 @@ index 40e87cd..93aac74 100644 checkFinishedScrollFallback(ctx); if (state.props.onMomentumScrollEnd) { state.props.onMomentumScrollEnd(event); -@@ -7630,6 +7855,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7630,6 +7893,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded onScroll: (event) => onScroll(ctx, event), onScrollBeginDrag: (event) => { var _a4, _b2; @@ -1357,7 +1529,7 @@ index 40e87cd..93aac74 100644 prepareReachedEdgeForNextUserScroll(ctx); (_b2 = (_a4 = state.props).onScrollBeginDrag) == null ? void 0 : _b2.call(_a4, event); }, -@@ -7655,11 +7882,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded +@@ -7655,11 +7920,18 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded ListFooterComponent, ListFooterComponentStyle, ListHeaderComponent, @@ -1377,7 +1549,7 @@ index 40e87cd..93aac74 100644 recycleItems, refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2.cloneElement(refreshControlElement, { diff --git a/react-native.web.js b/react-native.web.js -index 914d2da..8b88b09 100644 +index 914d2dafaafa001c9ab6791a0a0581659295e333..8b88b09d1269acef77c6ce0f8aaa1049d4fa26e9 100644 --- a/react-native.web.js +++ b/react-native.web.js @@ -5651,6 +5651,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { @@ -1412,7 +1584,7 @@ index 914d2da..8b88b09 100644 } } diff --git a/react-native.web.mjs b/react-native.web.mjs -index 95465f2..d7cbe1c 100644 +index 95465f2ab89ce41a10553f58af83618f7310e83c..d7cbe1cb43449cc29822b2646deabbddd67f1e7d 100644 --- a/react-native.web.mjs +++ b/react-native.web.mjs @@ -5630,6 +5630,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { @@ -1447,7 +1619,7 @@ index 95465f2..d7cbe1c 100644 } } diff --git a/react.js b/react.js -index 914d2da..86dbd9a 100644 +index 914d2dafaafa001c9ab6791a0a0581659295e333..199aae77b3620b7c4bb2ecd96b82978c0430059a 100644 --- a/react.js +++ b/react.js @@ -4702,7 +4702,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { @@ -1483,7 +1655,39 @@ index 914d2da..86dbd9a 100644 setContainerLayoutBaseline(element, rect); measurements.push({ containerId, -@@ -5651,6 +5659,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { +@@ -4978,12 +4986,26 @@ var ContainerLayoutCoordinator = typedMemo(function ContainerLayoutCoordinatorCo + }) { + const ctx = useStateContext(); + const [containerLayoutEpoch] = useArr$(["containerLayoutEpoch"]); ++ const layoutPasses = React3__namespace.useRef(0); + React3__namespace.useLayoutEffect(() => { +- { +- const targetContainerIds = getContainerLayoutEffectScope(ctx); +- if (targetContainerIds !== void 0) { +- measureContainersInLayoutEffect(ctx, targetContainerIds); +- } ++ const targetContainerIds = getContainerLayoutEffectScope(ctx); ++ if (targetContainerIds === void 0) return; ++ if (layoutPasses.current === 0) { ++ queueMicrotask(() => { layoutPasses.current = 0; }); ++ } ++ // Correcting large estimates can mount one row per commit. Bound the ++ // synchronous chain below React's nested-update limit, then keep measuring ++ // the latest pending rows on the next frame. Idle lists schedule no work. ++ if (layoutPasses.current++ < 8) { ++ measureContainersInLayoutEffect(ctx, targetContainerIds); ++ } else { ++ ctx.pendingContainerIds = targetContainerIds; ++ ctx.state.scheduledWork.frame(() => { ++ const pendingIds = getContainerLayoutEffectScope(ctx); ++ if (pendingIds !== void 0) { ++ measureContainersInLayoutEffect(ctx, pendingIds); ++ } ++ }, "containerLayout"); + } + }, [ctx, containerLayoutEpoch]); + return children; +@@ -5651,6 +5673,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { }); // src/utils/reordering.ts @@ -1502,7 +1706,7 @@ index 914d2da..86dbd9a 100644 function sortDOMElements(container, indexByElement) { const elements = Array.from(container.children); if (elements.length <= 1) return elements; -@@ -5690,9 +5710,9 @@ function sortDOMElements(container, indexByElement) { +@@ -5690,9 +5724,9 @@ function sortDOMElements(container, indexByElement) { } } if (nextStableElement) { @@ -1514,7 +1718,7 @@ index 914d2da..86dbd9a 100644 } } } -@@ -6446,8 +6466,8 @@ function ScrollAdjust() { +@@ -6446,8 +6480,8 @@ function ScrollAdjust() { window.getComputedStyle(contentNode)[axis.paddingEndProp] ); const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; @@ -1525,7 +1729,7 @@ index 914d2da..86dbd9a 100644 scrollBy(); if (resetPaddingRafRef.current !== void 0) { diff --git a/react.mjs b/react.mjs -index 95465f2..cff49dc 100644 +index 95465f2ab89ce41a10553f58af83618f7310e83c..a123bd3e9df510100d44dfae1f59b87e2e456cfc 100644 --- a/react.mjs +++ b/react.mjs @@ -4681,7 +4681,8 @@ function maybeUpdateAnchoredEndSpace(ctx) { @@ -1561,7 +1765,39 @@ index 95465f2..cff49dc 100644 setContainerLayoutBaseline(element, rect); measurements.push({ containerId, -@@ -5630,6 +5638,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { +@@ -4957,12 +4965,26 @@ var ContainerLayoutCoordinator = typedMemo(function ContainerLayoutCoordinatorCo + }) { + const ctx = useStateContext(); + const [containerLayoutEpoch] = useArr$(["containerLayoutEpoch"]); ++ const layoutPasses = React3.useRef(0); + React3.useLayoutEffect(() => { +- { +- const targetContainerIds = getContainerLayoutEffectScope(ctx); +- if (targetContainerIds !== void 0) { +- measureContainersInLayoutEffect(ctx, targetContainerIds); +- } ++ const targetContainerIds = getContainerLayoutEffectScope(ctx); ++ if (targetContainerIds === void 0) return; ++ if (layoutPasses.current === 0) { ++ queueMicrotask(() => { layoutPasses.current = 0; }); ++ } ++ // Correcting large estimates can mount one row per commit. Bound the ++ // synchronous chain below React's nested-update limit, then keep measuring ++ // the latest pending rows on the next frame. Idle lists schedule no work. ++ if (layoutPasses.current++ < 8) { ++ measureContainersInLayoutEffect(ctx, targetContainerIds); ++ } else { ++ ctx.pendingContainerIds = targetContainerIds; ++ ctx.state.scheduledWork.frame(() => { ++ const pendingIds = getContainerLayoutEffectScope(ctx); ++ if (pendingIds !== void 0) { ++ measureContainersInLayoutEffect(ctx, pendingIds); ++ } ++ }, "containerLayout"); + } + }, [ctx, containerLayoutEpoch]); + return children; +@@ -5630,6 +5652,18 @@ var ContainerSlot = typedMemo(function ContainerSlot2(props) { }); // src/utils/reordering.ts @@ -1580,7 +1816,7 @@ index 95465f2..cff49dc 100644 function sortDOMElements(container, indexByElement) { const elements = Array.from(container.children); if (elements.length <= 1) return elements; -@@ -5669,9 +5689,9 @@ function sortDOMElements(container, indexByElement) { +@@ -5669,9 +5703,9 @@ function sortDOMElements(container, indexByElement) { } } if (nextStableElement) { @@ -1592,7 +1828,7 @@ index 95465f2..cff49dc 100644 } } } -@@ -6425,8 +6445,8 @@ function ScrollAdjust() { +@@ -6425,8 +6459,8 @@ function ScrollAdjust() { window.getComputedStyle(contentNode)[axis.paddingEndProp] ); const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; @@ -1603,10 +1839,22 @@ index 95465f2..cff49dc 100644 scrollBy(); if (resetPaddingRafRef.current !== void 0) { diff --git a/reanimated.d.ts b/reanimated.d.ts -index e504332..2ce6383 100644 +index e5043320700b12f34f4c0babbc341f85ca8135c1..e8cbd461325a2e2e9ffd61de26a85ae4d108555c 100644 --- a/reanimated.d.ts +++ b/reanimated.d.ts -@@ -294,6 +294,12 @@ interface LegendListSpecificProps { +@@ -285,6 +285,11 @@ interface LegendListSpecificProps { + * - true enables both behaviors; false disables both. + */ + maintainVisibleContentPosition?: boolean | MaintainVisibleContentPositionConfig; ++ /** ++ * T3 patch: the scroll offset a change in item or list size should leave, read in the same pass ++ * that repositions the items so the adjustment commits with them. `undefined` keeps the default. ++ */ ++ sizeChangeAnchor?: () => number | undefined; + /** + * Keeps an item visually anchored to the start by adding trailing space when the content below it underflows. + */ +@@ -294,6 +299,12 @@ interface LegendListSpecificProps { * The adjustment is also rendered as real content padding so the browser scroll range includes it. */ contentInsetEndAdjustment?: number; @@ -1620,7 +1868,7 @@ index e504332..2ce6383 100644 * Number of columns to render items in. * @default 1 diff --git a/reanimated.js b/reanimated.js -index f1265fa..fc03be2 100644 +index f1265fad74189591b5aae86cf2e3a31f9c0fdb02..fc03be2190c046f743ffde45ed189ee7868d0cec 100644 --- a/reanimated.js +++ b/reanimated.js @@ -115,8 +115,10 @@ var ReanimatedPositionView = typedMemo(function ReanimatedPositionViewComponent( @@ -1783,7 +2031,7 @@ index f1265fa..fc03be2 100644 renderScrollComponent: renderReanimatedScrollComponent, ...IsNewArchitecture ? { stickyPositionComponentInternal } : {} diff --git a/reanimated.mjs b/reanimated.mjs -index 29a00d5..6ab9292 100644 +index 29a00d5dc084fd408a0e0a0a4d64e856d0ed1525..6ab929250c8924e2d9919ee09a4f774238fbb90a 100644 --- a/reanimated.mjs +++ b/reanimated.mjs @@ -1,7 +1,7 @@ diff --git a/patches/@react-navigation%2Fnative-stack@7.21.0.patch b/patches/@react-navigation%2Fnative-stack@7.21.0.patch index d221ce6a8754e..3fc4b61604e33 100644 --- a/patches/@react-navigation%2Fnative-stack@7.21.0.patch +++ b/patches/@react-navigation%2Fnative-stack@7.21.0.patch @@ -1,5 +1,5 @@ diff --git a/lib/module/views/NativeStackView.native.js b/lib/module/views/NativeStackView.native.js -index 6882090..011ad0e 100644 +index 6882090..f1e14e1 100644 --- a/lib/module/views/NativeStackView.native.js +++ b/lib/module/views/NativeStackView.native.js @@ -17,8 +17,12 @@ const ANDROID_DEFAULT_HEADER_HEIGHT = 56; @@ -15,7 +15,15 @@ index 6882090..011ad0e 100644 index, focused, shouldFreeze, -@@ -305,7 +309,8 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ +@@ -144,6 +148,7 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ + sheetAllowedDetents = [1.0], + sheetLargestUndimmedDetentIndex = -1, + sheetGrabberVisible = false, ++ sheetPrefersHorizontalBars = false, + sheetCornerRadius = -1.0, + sheetElevation = 24, + sheetExpandsWhenScrolledToEdge = true, +@@ -305,7 +310,8 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ } } }); @@ -25,7 +33,15 @@ index 6882090..011ad0e 100644 route: route, navigation: navigation, children: /*#__PURE__*/_jsx(ScreenStackItem, { -@@ -427,6 +432,7 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ +@@ -333,6 +339,7 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ + sheetAllowedDetents: sheetAllowedDetents, + sheetLargestUndimmedDetentIndex: sheetLargestUndimmedDetentIndex, + sheetGrabberVisible: sheetGrabberVisible, ++ sheetPrefersHorizontalBars: sheetPrefersHorizontalBars, + sheetInitialDetentIndex: sheetInitialDetentIndex, + sheetCornerRadius: sheetCornerRadius, + sheetElevation: sheetElevation, +@@ -427,6 +434,7 @@ const SceneView = /*#__PURE__*/React.memo(function SceneView({ }); }); export function NativeStackView({ @@ -33,7 +49,7 @@ index 6882090..011ad0e 100644 state, navigation, descriptors, -@@ -444,6 +450,17 @@ export function NativeStackView({ +@@ -444,6 +452,17 @@ export function NativeStackView({ return /*#__PURE__*/_jsx(SafeAreaProviderCompat, { children: /*#__PURE__*/_jsx(ScreenStack, { style: styles.container, @@ -51,7 +67,7 @@ index 6882090..011ad0e 100644 children: state.routes.concat(state.preloadedRoutes).map((route, index) => { const descriptor = descriptors[route.key] ?? preloadedDescriptors[route.key]; const isFocused = state.index === index; -@@ -460,6 +477,7 @@ export function NativeStackView({ +@@ -460,6 +479,7 @@ export function NativeStackView({ // due to component being unmounted. To avoid this, we don't freeze the previous screen there const shouldFreeze = isFabric() ? !isPreloaded && !isFocused && !isBelowFocused && !isModalOnIos : !isPreloaded && !isFocused && !isModalOnIos; return /*#__PURE__*/_jsx(SceneView, { @@ -60,7 +76,7 @@ index 6882090..011ad0e 100644 focused: isFocused, shouldFreeze: shouldFreeze, diff --git a/lib/module/views/useHeaderConfigProps.js b/lib/module/views/useHeaderConfigProps.js -index 08055b2..8718847 100644 +index 08055b2..ccf1fde 100644 --- a/lib/module/views/useHeaderConfigProps.js +++ b/lib/module/views/useHeaderConfigProps.js @@ -18,6 +18,12 @@ const processBarButtonItems = (items, colors, fonts) => { @@ -174,10 +190,19 @@ index 08055b2..8718847 100644 headerRightBarButtonItems: processBarButtonItems(rightItems, colors, fonts), experimental_userInterfaceStyle: dark ? 'dark' : 'light' diff --git a/lib/typescript/src/types.d.ts b/lib/typescript/src/types.d.ts -index 27a3d8e..3adf86e 100644 +index 27a3d8e..7312e4e 100644 --- a/lib/typescript/src/types.d.ts +++ b/lib/typescript/src/types.d.ts -@@ -903,6 +903,8 @@ type SharedHeaderItem = { +@@ -694,6 +694,8 @@ export type NativeStackNavigationOptions = { + * @platform ios + */ + sheetGrabberVisible?: boolean; ++ /** Requests horizontal bars on iOS 27.1+. Defaults to UIKit automatic placement. */ ++ sheetPrefersHorizontalBars?: boolean; + /** + * The largest sheet detent for which a view underneath won't be dimmed. + * Works only when `presentation` is set to `formSheet`. +@@ -903,6 +905,8 @@ type SharedHeaderItem = { * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/sharesbackground */ sharesBackground?: boolean; @@ -186,7 +211,7 @@ index 27a3d8e..3adf86e 100644 /** * An identifier used to match items across transitions. * Only available from iOS 26.0 and later. -@@ -1148,6 +1150,13 @@ export type NativeStackHeaderItemSpacing = { +@@ -1148,6 +1152,13 @@ export type NativeStackHeaderItemSpacing = { */ export type NativeStackHeaderItemCustom = { type: 'custom'; @@ -232,10 +257,19 @@ index d533cd9..857b7ad 100644 //# sourceMappingURL=NativeStackView.native.d.ts.map \ No newline at end of file diff --git a/src/types.tsx b/src/types.tsx -index 5d63f41..e9c53ee 100644 +index 5d63f41..f949a11 100644 --- a/src/types.tsx +++ b/src/types.tsx -@@ -959,6 +959,8 @@ type SharedHeaderItem = { +@@ -746,6 +746,8 @@ export type NativeStackNavigationOptions = { + * @platform ios + */ + sheetGrabberVisible?: boolean; ++ /** Requests horizontal bars on iOS 27.1+. Defaults to UIKit automatic placement. */ ++ sheetPrefersHorizontalBars?: boolean; + /** + * The largest sheet detent for which a view underneath won't be dimmed. + * Works only when `presentation` is set to `formSheet`. +@@ -959,6 +961,8 @@ type SharedHeaderItem = { * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/sharesbackground */ sharesBackground?: boolean; @@ -244,7 +278,7 @@ index 5d63f41..e9c53ee 100644 /** * An identifier used to match items across transitions. * Only available from iOS 26.0 and later. -@@ -1213,6 +1215,13 @@ export type NativeStackHeaderItemSpacing = { +@@ -1213,6 +1217,13 @@ export type NativeStackHeaderItemSpacing = { */ export type NativeStackHeaderItemCustom = { type: 'custom'; @@ -259,7 +293,7 @@ index 5d63f41..e9c53ee 100644 * A React Element to display as the item. */ diff --git a/src/views/NativeStackView.native.tsx b/src/views/NativeStackView.native.tsx -index aa13bc7..ab4a9d1 100644 +index aa13bc7..225daf9 100644 --- a/src/views/NativeStackView.native.tsx +++ b/src/views/NativeStackView.native.tsx @@ -52,7 +52,14 @@ function isFabric() { @@ -285,7 +319,15 @@ index aa13bc7..ab4a9d1 100644 index, focused, shouldFreeze, -@@ -434,7 +442,7 @@ const SceneView = React.memo(function SceneView({ +@@ -199,6 +207,7 @@ const SceneView = React.memo(function SceneView({ + sheetAllowedDetents = [1.0], + sheetLargestUndimmedDetentIndex = -1, + sheetGrabberVisible = false, ++ sheetPrefersHorizontalBars = false, + sheetCornerRadius = -1.0, + sheetElevation = 24, + sheetExpandsWhenScrolledToEdge = true, +@@ -434,7 +443,7 @@ const SceneView = React.memo(function SceneView({ ); return ( @@ -294,7 +336,15 @@ index aa13bc7..ab4a9d1 100644 @@ -308,7 +358,7 @@ index aa13bc7..ab4a9d1 100644 state: StackNavigationState; navigation: NativeStackNavigationHelpers; descriptors: NativeStackDescriptorMap; -@@ -586,6 +595,7 @@ type Props = { +@@ -586,6 +597,7 @@ type Props = { }; export function NativeStackView({ @@ -316,7 +366,7 @@ index aa13bc7..ab4a9d1 100644 state, navigation, descriptors, -@@ -633,6 +643,7 @@ export function NativeStackView({ +@@ -633,6 +645,7 @@ export function NativeStackView({ return ( &node) { + const auto componentName = node->current.componentName; + react_native_assert(componentName && "Component name is nullptr"); + return !std::strcmp(componentName, "RNSScreenStack") || !std::strcmp(componentName, "RNSScreen") || +- !std::strcmp(componentName, "RNSModalScreen"); ++ !std::strcmp(componentName, "RNSModalScreen") || !std::strcmp(componentName, "RNSStackScreen") || ++ !std::strcmp(componentName, "RNSSplitScreen") || !std::strcmp(componentName, "RNSTabsScreenIOS") || ++ !std::strcmp(componentName, "RNSTabsScreenAndroid"); + } + + static inline bool isRNSScreen(const std::shared_ptr &node) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa96b326cc4c0..c004aa9a0c300 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,9 +176,6 @@ catalogs: '@effect/sql-sqlite-do': specifier: 4.0.5 version: 4.0.5 - '@effect/tsgo': - specifier: 0.51.1 - version: 0.51.1 '@legendapp/list': specifier: 3.3.5 version: 3.3.5 @@ -249,8 +246,8 @@ catalogs: specifier: 7.4.2 version: 7.4.2 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: npm:@tsc-rs/typescript@0.8.0 + version: 0.8.0 undici: specifier: 8.11.2 version: 8.11.2 @@ -324,7 +321,7 @@ overrides: packageExtensionsChecksum: sha256-EowbsmlBFaoO0Ypufw901RnvdyjKG5dcdk2FRbc7Dr0= -pnpmfileChecksum: sha256-lS/yS+IvX/sj4KLdjVeRsq/hRipJMILKWjIbzIC+czo= +pnpmfileChecksum: sha256-YzTn4CWqI4l/Czk2VAdpGztl9kv2T+Fnt/Gw1mJ7Lu0= patchedDependencies: '@clerk/expo-native-components@0.2.0': 7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4 @@ -333,11 +330,11 @@ patchedDependencies: '@expo/metro-config@58.0.14': 90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760 '@expo/ui@58.0.20': 5a26480373850c1a7943919aa21f904151b335a0cb01042109e0746def37923e '@ff-labs/fff-node@0.11.0': cc71fcec015f4d39bc96ae2e2bf6b0ca678124c722c66a7841efce9d34b5983c - '@legendapp/list@3.3.5': e6383c516a0d67b65333d6fd38d2203734bf24b054b0b0767b034189205aed32 + '@legendapp/list@3.3.5': 011a862a7116ec5b5f4bba17d37bc58c9b5a8f64a6a9f527345f316ce9c96e63 '@pierre/diffs@1.5.2': 2ec81ba1e8ce4681095a3473c2bf78e630417740c622720ef624febb467c25fc '@react-native-ai/apple@0.12.0': 2d09870c2848d185cb05b53ed823a46e12dba519324d8dd8e584e28731990f9d '@react-native-menu/menu@2.0.0': a4866ecfcd44f318043de839d20110c17e14909004900f002ffe12050376f2ba - '@react-navigation/native-stack@7.21.0': caae6552051bce91a85f567f3a08f9c8d25ce4c53f65a9391a67f016bb1d798e + '@react-navigation/native-stack@7.21.0': 990d575444c4af2ce8218b3b7a8280fe244c03abbdb32b822a2e862f791dbf42 alchemy@2.0.0-beta.80: bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd app-builder-lib@26.16.1: a28c2dfcfc2b90d6c6247e199e6537391db9ecd941854c650910d607442512d3 dbus-next@0.10.2: cfff57561b0ee59b5addb3b2e6c6f20906e967507a530ab67e8db8108e520ba4 @@ -354,6 +351,7 @@ patchedDependencies: react-native-image-viewing@0.2.2: 419f35ee91eac88ced9438058281db5eb09d9c799d8974c1172926068c9448cf react-native-keyboard-controller@1.22.7: f28d08f1233e3cce8b9324b4357d2b10f1ee040b8051bc87235b0fb95069b9bb react-native-pdf-jsi@5.0.0: 6f52854210f61bef499850ebd498229a111bc34ec5b2a4925e644f2f48f42760 + react-native-reanimated@4.7.1: 95ee26e5fcab5f50bc4b4c47b3f7e6278a0b20850205efb9f6ee7e70a22d2b59 react-native-webview@14.0.1: 81caa279ec3dc1f81209062133b66e8f824e13f106cca57f1fa732a72c2ab378 importers: @@ -363,9 +361,6 @@ importers: '@coderabbitai/config': specifier: 1.1.0 version: 1.1.0 - '@effect/tsgo': - specifier: 'catalog:' - version: 0.51.1 '@shadcn/lint': specifier: 0.1.5 version: 0.1.5 @@ -375,15 +370,12 @@ importers: knip: specifier: 6.34.0 version: 6.34.0 - tsc-rs: - specifier: 0.7.0 - version: 0.7.0 typescript: specifier: 'catalog:' - version: 7.0.2 + version: '@tsc-rs/typescript@0.8.0' vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/desktop: dependencies: @@ -434,7 +426,7 @@ importers: version: 6.8.9(supports-color@8.1.1) electron-webauthn: specifier: 'catalog:' - version: 1.3.1(typescript@7.0.2) + version: 1.3.1(@tsc-rs/typescript@0.8.0) ffi-rs: specifier: 1.3.2 version: 1.3.2 @@ -450,7 +442,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -468,7 +460,7 @@ importers: version: 4.3.3 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/marketing: dependencies: @@ -496,10 +488,10 @@ importers: dependencies: '@clerk/expo': specifier: 4.13.0 - version: 4.13.0(f64b0f25957a972ead17046e3279ac85) + version: 4.13.0(0eb666c7fc01f4ee6d6438c43bfe16f6) '@clerk/expo-native-components': specifier: 0.2.0 - version: 0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) '@effect/atom-react': specifier: 4.0.5 version: 4.0.5(effect@4.0.5)(react@19.3.0)(scheduler@0.28.0) @@ -514,7 +506,7 @@ importers: version: 58.0.20(patch_hash=5a26480373850c1a7943919aa21f904151b335a0cb01042109e0746def37923e)(@babel/core@7.29.7(supports-color@8.1.1))(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@legendapp/list': specifier: 'catalog:' - version: 3.3.5(patch_hash=e6383c516a0d67b65333d6fd38d2203734bf24b054b0b0767b034189205aed32)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + version: 3.3.5(patch_hash=011a862a7116ec5b5f4bba17d37bc58c9b5a8f64a6a9f527345f316ce9c96e63)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@material/material-color-utilities': specifier: 0.3.0 version: 0.3.0 @@ -541,7 +533,7 @@ importers: version: 7.5.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@react-navigation/native-stack': specifier: 7.21.0 - version: 7.21.0(patch_hash=caae6552051bce91a85f567f3a08f9c8d25ce4c53f65a9391a67f016bb1d798e)(1ce92c52c0c48e16a6deb7b7b0829a25) + version: 7.21.0(patch_hash=990d575444c4af2ce8218b3b7a8280fe244c03abbdb32b822a2e862f791dbf42)(717295d9841cbe75b29e7bc95e2e9d14) '@shikijs/core': specifier: 4.2.0 version: 4.2.0 @@ -607,19 +599,19 @@ importers: version: 4.0.5 expo: specifier: 58.0.14 - version: 58.0.14(f758e93be082e96fe84cde9538c0a615) + version: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-application: specifier: ~58.1.2 version: 58.1.2(expo@58.0.14) expo-asset: specifier: ~58.0.16 - version: 58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-audio: specifier: 58.0.9 - version: 58.0.9(patch_hash=2c2f8c73a23d48804c8b537b8cd73c25eb331aa47d82eaa481c6766cb81e5add)(expo-asset@58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + version: 58.0.9(patch_hash=2c2f8c73a23d48804c8b537b8cd73c25eb331aa47d82eaa481c6766cb81e5add)(expo-asset@58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) expo-auth-session: specifier: ~58.0.13 - version: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-blur: specifier: 58.0.4 version: 58.0.4(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) @@ -634,7 +626,7 @@ importers: version: 58.0.3(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) expo-constants: specifier: 58.0.12 - version: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) expo-crypto: specifier: ~58.0.7 version: 58.0.7(expo@58.0.14) @@ -673,19 +665,19 @@ importers: version: 58.0.3(expo@58.0.14)(react@19.3.0) expo-linking: specifier: ~58.0.13 - version: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-network: specifier: ~58.0.3 version: 58.0.3(patch_hash=cd09b6d1744dde4efa081b5619ebcff38877d98f7502a51cab121629dece8237)(expo@58.0.14)(react@19.3.0) expo-notifications: specifier: ~58.1.5 - version: 58.1.5(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.1.5(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-paste-input: specifier: ^0.1.15 version: 0.1.15(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) expo-quick-actions: specifier: ^6.0.2 - version: 6.0.2(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2) + version: 6.0.2(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(supports-color@8.1.1) expo-secure-store: specifier: ~58.0.2 version: 58.0.2(expo@58.0.14) @@ -694,10 +686,10 @@ importers: version: 58.0.4(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) expo-sharing: specifier: 58.0.19 - version: 58.0.19(patch_hash=61a9741dc6166fdbb5baa2e489275a65dd21aa11d2c915f490a1b337a4468e76)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.19(patch_hash=61a9741dc6166fdbb5baa2e489275a65dd21aa11d2c915f490a1b337a4468e76)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-splash-screen: specifier: ~58.0.10 - version: 58.0.10(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2) + version: 58.0.10(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(supports-color@8.1.1) expo-sqlite: specifier: ~58.0.13 version: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) @@ -739,7 +731,7 @@ importers: version: 0.2.2(patch_hash=419f35ee91eac88ced9438058281db5eb09d9c799d8974c1172926068c9448cf)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-keyboard-controller: specifier: 1.22.7 - version: 1.22.7(patch_hash=f28d08f1233e3cce8b9324b4357d2b10f1ee040b8051bc87235b0fb95069b9bb)(312dd38e5b3f33a4cd42485c56107a1a) + version: 1.22.7(patch_hash=f28d08f1233e3cce8b9324b4357d2b10f1ee040b8051bc87235b0fb95069b9bb)(e2a826d844fe10cdcbda558127861778) react-native-nitro-modules: specifier: 0.37.1 version: 0.37.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) @@ -748,13 +740,13 @@ importers: version: 5.0.0(patch_hash=6f52854210f61bef499850ebd498229a111bc34ec5b2a4925e644f2f48f42760)(ddae2ea06f4d260467f86a139b5aa8e9) react-native-reanimated: specifier: 4.7.1 - version: 4.7.1(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + version: 4.7.1(patch_hash=95ee26e5fcab5f50bc4b4c47b3f7e6278a0b20850205efb9f6ee7e70a22d2b59)(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-safe-area-context: specifier: ~5.10.1 version: 5.10.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-screens: - specifier: file:deps/react-native-screens-5.0.0-t3.30.tgz - version: file:apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + specifier: file:deps/react-native-screens-5.0.0-t3.36.tgz + version: file:apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-shiki-engine: specifier: ^0.3.12 version: 0.3.12(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) @@ -784,7 +776,7 @@ importers: version: 11.0.5 uniwind: specifier: 1.13.0 - version: 1.13.0(@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6))(metro-cache@0.87.1(supports-color@8.1.1))(metro-transform-worker@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(tailwindcss@4.3.3) + version: 1.13.0(@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro-cache@0.87.1(supports-color@8.1.1))(metro-transform-worker@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(tailwindcss@4.3.3) devDependencies: '@babel/core': specifier: ^7.29.7 @@ -794,7 +786,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@pierre/trees': specifier: 1.0.0-beta.6 version: 1.0.0-beta.6(@pierre/theme@2.0.0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(shiki@4.2.0) @@ -818,7 +810,7 @@ importers: version: 4.3.3 typescript: specifier: 'catalog:' - version: 7.0.2 + version: '@tsc-rs/typescript@0.8.0' apps/server: dependencies: @@ -927,7 +919,7 @@ importers: version: 4.0.5(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.0.5) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@modelcontextprotocol/sdk': specifier: 1.29.0 version: 1.29.0(supports-color@8.1.1)(zod@4.6.5) @@ -972,7 +964,7 @@ importers: version: 16.14.1 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/web: dependencies: @@ -1011,7 +1003,7 @@ importers: version: 0.9.0 '@legendapp/list': specifier: 'catalog:' - version: 3.3.5(patch_hash=e6383c516a0d67b65333d6fd38d2203734bf24b054b0b0767b034189205aed32)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + version: 3.3.5(patch_hash=011a862a7116ec5b5f4bba17d37bc58c9b5a8f64a6a9f527345f316ce9c96e63)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@noble/hashes': specifier: 'catalog:' version: 1.8.0 @@ -1180,13 +1172,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@tailwindcss/vite': specifier: 4.3.3 - version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)) '@tanstack/router-plugin': specifier: ^1.161.0 - version: 1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(supports-color@8.1.1) + version: 1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(supports-color@8.1.1) '@types/compression': specifier: ^1.8.1 version: 1.8.1 @@ -1210,10 +1202,10 @@ importers: version: 0.3.0 '@vitejs/plugin-react': specifier: ^6.1.2 - version: 6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0) + version: 6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0) '@vitest/browser-playwright': specifier: 5.0.5 - version: 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) + version: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) compression: specifier: ^1.8.2 version: 1.8.2(supports-color@8.1.1) @@ -1228,10 +1220,10 @@ importers: version: 4.3.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + version: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) infra/relay: dependencies: @@ -1264,10 +1256,10 @@ importers: version: link:../../packages/shared alchemy: specifier: 2.0.0-beta.80 - version: 2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(c76a5fb0ee7617c9ee4d0d91c51d5e28) + version: 2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(2676e7bf1eae706a0c10853e80d0a1b0) drizzle-orm: specifier: 1.0.0-rc.5-ab785fc - version: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(valibot@1.2.0(typescript@7.0.2))(zod@4.4.3) + version: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(zod@4.4.3) effect: specifier: 4.0.5 version: 4.0.5 @@ -1280,7 +1272,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1289,13 +1281,13 @@ importers: version: 1.0.0-rc.5-ab785fc typescript: specifier: 'catalog:' - version: 7.0.2 + version: '@tsc-rs/typescript@0.8.0' vite: specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + version: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) oxlint-plugin-t3code: dependencies: @@ -1311,10 +1303,10 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/client-runtime: dependencies: @@ -1357,13 +1349,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/three': specifier: 0.180.0 version: 0.180.0 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/contracts: dependencies: @@ -1373,10 +1365,10 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/effect-acp: dependencies: @@ -1392,13 +1384,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/effect-codex-app-server: dependencies: @@ -1414,13 +1406,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-acp: dependencies: @@ -1445,7 +1437,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1454,7 +1446,7 @@ importers: version: link:../effect-acp vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-acp-registry: dependencies: @@ -1482,7 +1474,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1494,7 +1486,7 @@ importers: version: link:../effect-acp vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-core: dependencies: @@ -1513,13 +1505,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-cursor: dependencies: @@ -1550,7 +1542,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1559,7 +1551,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-grok: dependencies: @@ -1584,7 +1576,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1596,7 +1588,7 @@ importers: version: link:../effect-acp vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-muse: dependencies: @@ -1621,7 +1613,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1630,7 +1622,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-opencode: dependencies: @@ -1664,7 +1656,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1673,7 +1665,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-pi: dependencies: @@ -1695,7 +1687,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1704,7 +1696,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-testing: dependencies: @@ -1790,7 +1782,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -1802,7 +1794,7 @@ importers: version: 2.0.2 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-azure-devops: dependencies: @@ -1827,7 +1819,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1836,7 +1828,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-bitbucket: dependencies: @@ -1858,13 +1850,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-core: dependencies: @@ -1880,13 +1872,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-forgejo: dependencies: @@ -1908,7 +1900,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1917,7 +1909,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-gitcafe: dependencies: @@ -1939,7 +1931,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1948,7 +1940,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-github: dependencies: @@ -1976,7 +1968,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1985,7 +1977,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-gitlab: dependencies: @@ -2004,7 +1996,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2013,7 +2005,7 @@ importers: version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-testing: dependencies: @@ -2051,13 +2043,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/tailscale: dependencies: @@ -2070,13 +2062,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) scripts: dependencies: @@ -2110,7 +2102,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/plist': specifier: 'catalog:' version: 3.0.5 @@ -2122,13 +2114,13 @@ importers: version: 0.28.2 typescript: specifier: 'catalog:' - version: 7.0.2 + version: '@tsc-rs/typescript@0.8.0' typescript-legacy: specifier: npm:typescript@~6.0.3 version: typescript@6.0.3 vite-plus: specifier: 1.3.0 - version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages: @@ -3377,45 +3369,6 @@ packages: peerDependencies: effect: 4.0.5 - '@effect/tsgo-darwin-arm64@0.51.1': - resolution: {integrity: sha512-Jx+1RB3pz1B2zYs1EmxybwDOq9KRpfdu/rn1XTULNMZmqUJZuvTQU9CRqB0g/ZgIa6MeNBZEYvmAu4Dq2/QVWA==} - cpu: [arm64] - os: [darwin] - - '@effect/tsgo-darwin-x64@0.51.1': - resolution: {integrity: sha512-eRz7eHOOb2eJPUj66m89Zhzu924vGvNLDAXzcEAIaXVsWc4Zw29RVUsRnp/ozTmCbDVCvMqPSh8i9bkdAmt8mA==} - cpu: [x64] - os: [darwin] - - '@effect/tsgo-linux-arm64@0.51.1': - resolution: {integrity: sha512-LqZKkEEgJcq7dOho+h0esioU/idlB/+VhPuMR7l9B5Y7mk17Eivs8TiJQHtxLVfrh/nizbvZih7O5GujRNG0bw==} - cpu: [arm64] - os: [linux] - - '@effect/tsgo-linux-arm@0.51.1': - resolution: {integrity: sha512-vub6MCJIbczHp+Im1GTEUh5YyS+6YHcvH0ZmBdSjdvqzzBKbRnl5xUbO52tRJlty74f+3ey+uSh0nIO99NnggA==} - cpu: [arm] - os: [linux] - - '@effect/tsgo-linux-x64@0.51.1': - resolution: {integrity: sha512-zyKKcdL2Pr4C//9raggaEuxhsSUR7jT6517vHvPwykbBPjPnnmGtM1Kq7NKEIZnvXWzXcaK+4wAK/0FqSqMOmw==} - cpu: [x64] - os: [linux] - - '@effect/tsgo-win32-arm64@0.51.1': - resolution: {integrity: sha512-yIF88Za3Bs793Mb8OShet8gc3kj/NNty6+ZoTdgiPXWlY6xh84bZZCOETqE7VcCmxXFT3NZSYQuDrU35/CTvRQ==} - cpu: [arm64] - os: [win32] - - '@effect/tsgo-win32-x64@0.51.1': - resolution: {integrity: sha512-5HO1TJkWMlErIXbfKKWEMzg0Jj0MPQAzs0g1k7wA3B1ZxNDoDY9iRu5eGM+G1a4HmKl3zA7hQZd0ch2Dh4MohQ==} - cpu: [x64] - os: [win32] - - '@effect/tsgo@0.51.1': - resolution: {integrity: sha512-i6mA6cNah+0mkPdu8qZILCO3Pnd9kWo03/s82Ju+1DFhdL28HFagi+1PRGxqgq1uMaHziWQ+ciKvOmIoks2RdA==} - hasBin: true - '@effect/vitest@4.0.5': resolution: {integrity: sha512-q64paZ/i4V2NiRhTyqCmT2KiDbFjlxc2EZrZca8GGt+Nf8844946HyJM5bPk/q+YeRkFSxIrlCs+J+LSfky/wA==} peerDependencies: @@ -4338,41 +4291,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} - - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': 24.12.4 - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': 24.12.4 - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} - - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': 24.12.4 - peerDependenciesMeta: - '@types/node': - optional: true - '@ioredis/commands@1.10.0': resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} @@ -4508,10 +4426,6 @@ packages: '@mongodb-js/saslprep@1.4.13': resolution: {integrity: sha512-E3Sv4eCYAlKYUTx8S3ioQcDUscOif+8zZ5OnW1IzJ+Tt+EO+ke8mn+Y3FX6N1H79picwbdOavVOb1jPi2EOyrg==} - '@mswjs/interceptors@0.41.9': - resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} - engines: {node: '>=18'} - '@muse-code/sdk@1.4.6': resolution: {integrity: sha512-pUENCnmn9q4e2xMXicdRbzbSMdH1fiyTIh5fVBXuxNdNP2ZKA0kwQz5fWMxIB8wzxS2ZyH0v27dvfbZuGuoUjg==} engines: {node: '>=20'} @@ -4761,15 +4675,6 @@ packages: resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} engines: {node: '>= 20'} - '@open-draft/deferred-promise@2.2.0': - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - - '@open-draft/logger@0.3.0': - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - - '@open-draft/until@2.1.0': - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@opencode-ai/sdk@1.15.13': resolution: {integrity: sha512-4TwojIoQ8EG6/mVBuUVYZXiFcwNmiiytEnjnvyuvSJjGwFIlw2YIBFxtSVC3FbwwbwHT63teh1RHiQUUC4U5xw==} @@ -6545,38 +6450,43 @@ packages: '@tiptap/starter-kit@3.31.3': resolution: {integrity: sha512-WKof9RewdmGHvWJ1wn0/HVNG2mV+HOgVRyJkKekuM9fgr6BZAAH/xZsWE1eon+94JnQ+KtK2ThydXQM/qc6b2A==} - '@tsc-rs/darwin-arm64@0.7.0': - resolution: {integrity: sha512-3execGkNh7DXNZr03dhQP2wqmsjB1Q4OJnTMgKIibjeQMsjNK8HTalJZPIG6PkiXeusXUweHP3+4yJjbi5auNw==} + '@tsc-rs/darwin-arm64@0.8.0': + resolution: {integrity: sha512-1aGCULRzd7KXIFh72Ja1K+35bQ1AeW9P3F3S36MXXhp5EQCezqxeXKkL3kQ3kH+ebfI/wA2gxyus34rllF1keQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@tsc-rs/darwin-x64@0.7.0': - resolution: {integrity: sha512-Wi3CNYWgyIOcWTQjihXCFPTul+aTEXVq1og8rO22Cq6/Mz6lKk+9YSXycWOkJql6D0uE0l/S1nMStq4mx7ydpA==} + '@tsc-rs/darwin-x64@0.8.0': + resolution: {integrity: sha512-FFFoxnJwszgsM/u4XLH2FB0mzqg+TCCvy+NPCL6fYZv3sbJjoekB4w5gw8Esg9/IPjHbV1rBqNlXYnyCiKYkkQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@tsc-rs/linux-arm64@0.7.0': - resolution: {integrity: sha512-sm+xPELgQGKOoFeklJAmIi99Fi3fIUFgGPYQclC/IiVdUTzHtKw6yg6JRNEI74JIaSxGRAGbtmHcVvTkC93ZLw==} + '@tsc-rs/linux-arm64@0.8.0': + resolution: {integrity: sha512-mo8qBCCgvMv7jYJrYSbHjc5CBVkzMQiJFOVF6oL4nP7sp/QjywskFZwUduw8Yn1MX83fR1zbJbhAkKtlwRZ3hg==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@tsc-rs/linux-x64@0.7.0': - resolution: {integrity: sha512-gyDPxVNmasL/RMPXzutxhaUqyLlPLTcPhWl5pWCkuZdV0dUfGpR4PG5h1Nht9Db3MMHs9ixzl+uS6Nc5+qA/jg==} + '@tsc-rs/linux-x64@0.8.0': + resolution: {integrity: sha512-c1DPPfJ6F8HkScESepl42p4Cph8BcKcd3dBakYLBPyWiKZD8q3bw/kDNoAQoTnO3k8Zkv5FFbI85ot4y6gNQuA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@tsc-rs/win32-arm64@0.7.0': - resolution: {integrity: sha512-dPLJLH5iXgBzMRiuSGz4awMu8dxOI2iXRN0ZDqs/LolZ1ShbjmgIcH1bwQeObdyFwyen44c6vK8mFIiJELsv4Q==} + '@tsc-rs/typescript@0.8.0': + resolution: {integrity: sha512-bdPVWnuhvG2NmbxN9vbCBsG1bxnjgi0dK3sTyU+ZQ+d1Ea7xJUy4bniFJYkuw2GnCqb1lQGEn+zo2Si7gR9rYA==} + engines: {node: '>=16.20.0'} + hasBin: true + + '@tsc-rs/win32-arm64@0.8.0': + resolution: {integrity: sha512-JNycrZs0LcZ8Rx1+Ik3z9R7YkNIKE2ZQjWKmI6os3EpGrMpfSztFYhbuI0whiZpFfL0AqaVu4o0F1+oR0Z8YDQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@tsc-rs/win32-x64@0.7.0': - resolution: {integrity: sha512-n/bJeMWvr0KbBPYVXroe23ia82Q6ZizUBYhgoZvsUasK4RfZ/rsJdIoP1Jpcw0ONXEbC8AKwHVi8BSOiUlSOuw==} + '@tsc-rs/win32-x64@0.8.0': + resolution: {integrity: sha512-oWSjfTaOhdZjC8C0gJoa/LdF4TiH729UHKUYm7t2mvdBtPoC85NxvhTIC8ZFjaKs+bvzZ6uMOi8N2EWK2LCV3Q==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] @@ -6718,9 +6628,6 @@ packages: '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} - '@types/statuses@2.0.6': - resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/three@0.180.0': resolution: {integrity: sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==} @@ -6757,126 +6664,6 @@ packages: '@types/yauzl@3.4.0': resolution: {integrity: sha512-NRPn5w6h8dhcnmx3YIRQcqMywY/+nND/uOkJessedcrowO3C0AssHp3tMJpxKAwOhFOo0OV1y9VtsC5hbKKBAw==} - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} @@ -6923,12 +6710,9 @@ packages: resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: msw: ^2.4.9 || ^3.0.0 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true - vite: - optional: true '@vitest/pretty-format@5.0.5': resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} @@ -6967,7 +6751,7 @@ packages: sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 - typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + typescript: '*' unplugin-unused: '>=0.5.0' unrun: '*' vue-tsc: ~3.2.0 || ~3.3.0 @@ -7795,10 +7579,6 @@ packages: resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -7945,10 +7725,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - cookie@2.0.1: resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} engines: {node: '>=22'} @@ -9198,9 +8974,6 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - heic-to@1.5.2: resolution: {integrity: sha512-8Fns+lZHAWmz5U5IUxDeXKwIf3foBoKNPLxxFY4B0MkLjNuomEIHCoDbDE+x/llFK3NCEO1cu4+n3iUKY+Svmw==} @@ -9354,9 +9127,6 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -10133,26 +9903,12 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.11: - resolution: {integrity: sha512-dVg20zi2I2EvnwH/+WupzsOC2mCa7qsIhyMAWtfRikn6RKtwL9+7SaF1IQ5LyZry4tlUtf6KyTVhnlQiZXozTQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - typescript: '>= 4.8.x' - peerDependenciesMeta: - typescript: - optional: true - muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} multitars@1.0.2: resolution: {integrity: sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==} - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - nanoid@3.3.19: resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -10362,9 +10118,6 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} - outvariant@1.4.3: - resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxc-parser@0.147.0: resolution: {integrity: sha512-5xaug6t7GfV3BO5Iv+xHW1rmQkDEQ3BEu3L8g3InsvWO5i8CYGc4tCZ2X985QcwWNycFJam+aOns6Nr2XAThTA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -10902,9 +10655,9 @@ packages: react: '*' react-native: '*' - react-native-screens@file:apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz: - resolution: {integrity: sha512-Szxvbp1mt4KwJ6cLoirkLQ2kz7Ebia3fTYPTjTifwqw3djskueHmML1np+wSZEs3Y0NtZf681SG/8xN5iW3T5Q==, tarball: file:apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz} - version: 5.0.0-t3.30 + react-native-screens@file:apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz: + resolution: {integrity: sha512-Fkd/KBWVC8NrGc3sBL7JdK5Zn1+2cw5tU0NxXWlqdkjjar5BntiQNU+NBvALM+NUnB4q7oHykYCQjW4Nk6i67A==, tarball: file:apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz} + version: 5.0.0-t3.36 peerDependencies: react: '*' react-native: '*' @@ -11100,9 +10853,6 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - rettime@0.10.1: - resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} - rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -11414,9 +11164,6 @@ packages: stream-json@3.6.0: resolution: {integrity: sha512-NiJdqxKyau579z/E8vfqcjWfSDWxW/AT99javFXdPXF147Z5za85LRXSHEmSX9TKOakB7gaIccfD0fOIctb7KQ==} - strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -11639,11 +11386,6 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - tsc-rs@0.7.0: - resolution: {integrity: sha512-diVCBljoXLQjvXRkIuamR3QRrmNyqgYDePKA5SFXTSDPSjku2a2JXj/bOxG4DoPUbYkzWl/XFu1689z/46TCaA==} - engines: {node: '>=16.20.0'} - hasBin: true - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -11682,11 +11424,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} - hasBin: true - ua-parser-js@0.7.41: resolution: {integrity: sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==} hasBin: true @@ -11874,9 +11611,6 @@ packages: uploadthing: optional: true - until-async@3.0.2: - resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - unzipper@0.12.5: resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==} @@ -11918,14 +11652,6 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - validate-npm-package-name@5.0.1: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -12198,10 +11924,6 @@ packages: engines: {node: '>=16'} hasBin: true - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -12326,10 +12048,6 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -12387,9 +12105,9 @@ packages: snapshots: - 2g@1.0.1(typescript@7.0.2): + 2g@1.0.1(@tsc-rs/typescript@0.8.0): optionalDependencies: - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' '@ai-sdk/provider-utils@4.0.57(zod@4.4.3)': dependencies: @@ -12407,13 +12125,13 @@ snapshots: dependencies: json-schema: 0.4.0 - '@alchemy.run/cloudflare-runtime@2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2)': + '@alchemy.run/cloudflare-runtime@2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)': dependencies: '@alchemy.run/node-utils': 2.0.0-beta.80 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260918.1) '@distilled.cloud/cloudflare': 1.0.0-rc.13(effect@4.0.5) '@puppeteer/browsers': 3.2.2(yauzl@3.4.0) - capnp-es: 0.0.16(typescript@7.0.2) + capnp-es: 0.0.16(@tsc-rs/typescript@0.8.0) effect: 4.0.5 magic-string: 0.30.21 mime: 4.1.0 @@ -12424,7 +12142,7 @@ snapshots: optionalDependencies: '@effect/platform-node': 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) rolldown: 1.2.5 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: - '@types/node' - proxy-agent @@ -13451,10 +13169,10 @@ snapshots: electron-store: 8.2.0 react-dom: 19.3.0(react@19.3.0) - '@clerk/expo-native-components@0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2)': + '@clerk/expo-native-components@0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)': dependencies: - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) tslib: 2.8.1 @@ -13462,22 +13180,22 @@ snapshots: - supports-color - typescript - '@clerk/expo@4.13.0(f64b0f25957a972ead17046e3279ac85)': + '@clerk/expo@4.13.0(0eb666c7fc01f4ee6d6438c43bfe16f6)': dependencies: '@clerk/clerk-js': 6.40.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@clerk/react': 6.18.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@clerk/shared': 4.41.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) base-64: 1.0.0 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) react-native-url-polyfill: 4.0.0(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) tslib: 2.8.1 optionalDependencies: - '@clerk/expo-native-components': 0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) - expo-auth-session: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + '@clerk/expo-native-components': 0.2.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) + expo-auth-session: 58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) expo-crypto: 58.0.7(expo@58.0.14) expo-secure-store: 58.0.2(expo@58.0.14) expo-web-browser: 58.0.6(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) @@ -13803,41 +13521,10 @@ snapshots: dependencies: effect: 4.0.5 - '@effect/tsgo-darwin-arm64@0.51.1': - optional: true - - '@effect/tsgo-darwin-x64@0.51.1': - optional: true - - '@effect/tsgo-linux-arm64@0.51.1': - optional: true - - '@effect/tsgo-linux-arm@0.51.1': - optional: true - - '@effect/tsgo-linux-x64@0.51.1': - optional: true - - '@effect/tsgo-win32-arm64@0.51.1': - optional: true - - '@effect/tsgo-win32-x64@0.51.1': - optional: true - - '@effect/tsgo@0.51.1': - optionalDependencies: - '@effect/tsgo-darwin-arm64': 0.51.1 - '@effect/tsgo-darwin-x64': 0.51.1 - '@effect/tsgo-linux-arm': 0.51.1 - '@effect/tsgo-linux-arm64': 0.51.1 - '@effect/tsgo-linux-x64': 0.51.1 - '@effect/tsgo-win32-arm64': 0.51.1 - '@effect/tsgo-win32-x64': 0.51.1 - - '@effect/vitest@4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': + '@effect/vitest@4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': dependencies: effect: 4.0.5 - vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) transitivePeerDependencies: - '@arethetypeswrong/core' - '@astrojs/compiler-rs' @@ -13881,12 +13568,12 @@ snapshots: '@electron-internal/extract-zip@1.0.5': {} - '@electron-webauthn/macos@1.3.1(typescript@7.0.2)': + '@electron-webauthn/macos@1.3.1(@tsc-rs/typescript@0.8.0)': dependencies: '@electron-webauthn/types': 1.3.1 '@oslojs/webauthn': 1.0.0 objc-js: 1.5.0 - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' optional: true '@electron-webauthn/types@1.3.1': {} @@ -14251,29 +13938,29 @@ snapshots: '@expo-google-fonts/material-symbols@0.4.38': {} - '@expo/cli@58.1.11(122bb879cf862e703fa54111e57fbca5)': + '@expo/cli@58.1.11(5e998cea2b9eedfd9ad81915214fc418)': dependencies: - 2g: 1.0.1(typescript@7.0.2) + 2g: 1.0.1(@tsc-rs/typescript@0.8.0) '@expo/code-signing-certificates': 0.0.7 - '@expo/config': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/devcert': 1.2.2(supports-color@8.1.1) '@expo/env': 2.5.2(supports-color@8.1.1) - '@expo/image-utils': 0.12.5(supports-color@8.1.1)(typescript@7.0.2) - '@expo/inline-modules': 0.2.6(supports-color@8.1.1)(typescript@7.0.2) + '@expo/image-utils': 0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/inline-modules': 0.2.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/json-file': 11.2.1 '@expo/log-box': 58.0.12(@expo/dom-webview@58.0.2)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@expo/log-box-utils': 58.0.2 '@expo/metro': 58.0.0(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6) - '@expo/metro-file-map': 58.0.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6) + '@expo/metro-file-map': 58.0.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/osascript': 2.8.1 '@expo/package-manager': 1.14.2 '@expo/platforms': 58.0.1 '@expo/plist': 0.10.1 - '@expo/prebuild-config': 58.0.14(supports-color@8.1.1)(typescript@7.0.2) - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) - '@expo/router-server': 58.0.14(@expo/metro-runtime@58.0.14)(expo-constants@58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2))(expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(expo-server@58.0.5)(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1) + '@expo/prebuild-config': 58.0.14(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/router-server': 58.0.14(@expo/metro-runtime@58.0.14)(expo-constants@58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1))(expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(expo-server@58.0.5)(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1) '@expo/schema-utils': 58.0.2 '@expo/spawn-async': 1.8.0 '@expo/ws-tunnel': 2.0.1(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) @@ -14291,7 +13978,7 @@ snapshots: compression: 1.8.2(supports-color@8.1.1) connect: 3.7.0(supports-color@8.1.1) dnssd-advertise: 1.1.6 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-server: 58.0.5 fetch-nodeshim: 0.4.10 getenv: 2.0.0 @@ -14336,12 +14023,12 @@ snapshots: dependencies: node-forge: 1.4.0 - '@expo/config-plugins@58.0.9(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/config-plugins@58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: '@expo/config-types': 58.0.3 '@expo/json-file': 11.2.1 '@expo/plist': 0.10.1 - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/sdk-runtime-versions': 1.0.1 chalk: 4.1.2 debug: 4.4.3(supports-color@8.1.1) @@ -14357,13 +14044,13 @@ snapshots: '@expo/config-types@58.0.3': {} - '@expo/config@58.0.6(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/config@58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/config-types': 58.0.3 '@expo/json-file': 11.2.1 '@expo/platforms': 58.0.1 - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -14385,13 +14072,13 @@ snapshots: dependencies: chalk: 4.1.2 optionalDependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) '@expo/dom-webview@58.0.2(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)': dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -14419,9 +14106,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/image-utils@0.12.5(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/image-utils@0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 getenv: 2.0.0 @@ -14432,9 +14119,9 @@ snapshots: - supports-color - typescript - '@expo/image-utils@0.8.14(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/image-utils@0.8.14(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/require-utils': 55.0.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 55.0.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 getenv: 2.0.0 @@ -14445,9 +14132,9 @@ snapshots: - supports-color - typescript - '@expo/inline-modules@0.2.6(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/inline-modules@0.2.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript @@ -14457,9 +14144,9 @@ snapshots: '@babel/code-frame': 7.29.7 json5: 2.2.3 - '@expo/local-build-cache-provider@58.0.6(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/local-build-cache-provider@58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/config': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) chalk: 4.1.2 transitivePeerDependencies: - supports-color @@ -14476,20 +14163,20 @@ snapshots: react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) stacktrace-parser: 0.1.11 optionalDependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) - '@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6)': + '@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6)': dependencies: - 2g: 1.0.1(typescript@7.0.2) + 2g: 1.0.1(@tsc-rs/typescript@0.8.0) '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/generator': 7.29.7 - '@expo/config': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/env': 2.5.2(supports-color@8.1.1) '@expo/json-file': 11.2.1 '@expo/metro': 58.0.0(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) '@expo/platforms': 58.0.1 - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/remapping': 2.3.5 @@ -14509,16 +14196,16 @@ snapshots: postcss: 8.5.30 resolve-from: 5.0.0 optionalDependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) transitivePeerDependencies: - bufferutil - supports-color - typescript - utf-8-validate - '@expo/metro-file-map@58.0.5(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/metro-file-map@58.0.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - 2g: 1.0.1(typescript@7.0.2) + 2g: 1.0.1(@tsc-rs/typescript@0.8.0) debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 invariant: 2.2.4 @@ -14532,7 +14219,7 @@ snapshots: dependencies: '@expo/log-box': 58.0.12(@expo/dom-webview@58.0.2)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) anser: 1.4.10 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) pretty-format: 29.7.0 react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -14589,47 +14276,47 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@58.0.14(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/prebuild-config@58.0.14(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: - '@expo/config': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/config-types': 58.0.3 - '@expo/image-utils': 0.12.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/image-utils': 0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/json-file': 11.2.1 '@react-native/normalize-colors': 0.88.0 debug: 4.4.3(supports-color@8.1.1) - expo-modules-autolinking: 58.0.13(supports-color@8.1.1)(typescript@7.0.2) + expo-modules-autolinking: 58.0.13(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) resolve-from: 5.0.0 semver: 7.8.5 transitivePeerDependencies: - supports-color - typescript - '@expo/require-utils@55.0.5(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/require-utils@55.0.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) optionalDependencies: - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' transitivePeerDependencies: - supports-color - '@expo/require-utils@58.0.4(supports-color@8.1.1)(typescript@7.0.2)': + '@expo/require-utils@58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) optionalDependencies: - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' transitivePeerDependencies: - supports-color - '@expo/router-server@58.0.14(@expo/metro-runtime@58.0.14)(expo-constants@58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2))(expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(expo-server@58.0.5)(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1)': + '@expo/router-server@58.0.14(@expo/metro-runtime@58.0.14)(expo-constants@58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1))(expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(expo-server@58.0.5)(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1)': dependencies: debug: 4.4.3(supports-color@8.1.1) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) expo-font: 58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) expo-server: 58.0.5 react: 19.3.0 @@ -14651,7 +14338,7 @@ snapshots: ? '@expo/ui@58.0.20(patch_hash=5a26480373850c1a7943919aa21f904151b335a0cb01042109e0746def37923e)(@babel/core@7.29.7(supports-color@8.1.1))(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)' : dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) sf-symbols-typescript: 2.2.0 @@ -14842,39 +14529,6 @@ snapshots: '@img/sharp-win32-x64@0.35.4': optional: true - '@inquirer/ansi@1.0.2': - optional: true - - '@inquirer/confirm@5.1.21(@types/node@24.12.4)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@24.12.4) - '@inquirer/type': 3.0.10(@types/node@24.12.4) - optionalDependencies: - '@types/node': 24.12.4 - optional: true - - '@inquirer/core@10.3.2(@types/node@24.12.4)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.12.4) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.12.4 - optional: true - - '@inquirer/figures@1.0.15': - optional: true - - '@inquirer/type@3.0.10(@types/node@24.12.4)': - optionalDependencies: - '@types/node': 24.12.4 - optional: true - '@ioredis/commands@1.10.0': optional: true @@ -14925,7 +14579,7 @@ snapshots: dependencies: jsbi: 4.3.2 - '@legendapp/list@3.3.5(patch_hash=e6383c516a0d67b65333d6fd38d2203734bf24b054b0b0767b034189205aed32)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)': + '@legendapp/list@3.3.5(patch_hash=011a862a7116ec5b5f4bba17d37bc58c9b5a8f64a6a9f527345f316ce9c96e63)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)': dependencies: react: 19.3.0 use-sync-external-store: 1.6.0(react@19.3.0) @@ -15033,16 +14687,6 @@ snapshots: sparse-bitfield: 3.0.3 optional: true - '@mswjs/interceptors@0.41.9': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.3 - strict-event-emitter: 0.5.1 - optional: true - '@muse-code/sdk@1.4.6(@types/node@24.12.4)': optionalDependencies: '@types/node': 24.12.4 @@ -15242,18 +14886,6 @@ snapshots: '@octokit/request-error': 7.1.0 '@octokit/webhooks-methods': 6.0.0 - '@open-draft/deferred-promise@2.2.0': - optional: true - - '@open-draft/logger@0.3.0': - dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.3 - optional: true - - '@open-draft/until@2.1.0': - optional: true - '@opencode-ai/sdk@1.15.13': dependencies: cross-spawn: 7.0.6 @@ -15966,7 +15598,7 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) - '@react-navigation/native-stack@7.21.0(patch_hash=caae6552051bce91a85f567f3a08f9c8d25ce4c53f65a9391a67f016bb1d798e)(1ce92c52c0c48e16a6deb7b7b0829a25)': + '@react-navigation/native-stack@7.21.0(patch_hash=990d575444c4af2ce8218b3b7a8280fe244c03abbdb32b822a2e862f791dbf42)(717295d9841cbe75b29e7bc95e2e9d14)': dependencies: '@react-navigation/elements': 2.9.45(d8845bac868f4d8a58c8a8f12ad0d4a5) '@react-navigation/native': 7.5.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) @@ -15974,7 +15606,7 @@ snapshots: react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) react-native-safe-area-context: 5.10.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) - react-native-screens: file:apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + react-native-screens: file:apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 transitivePeerDependencies: @@ -16328,12 +15960,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' '@tanstack/devtools-event-client@0.4.3': {} @@ -16396,7 +16028,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(supports-color@8.1.1)': + '@tanstack/router-plugin@1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(supports-color@8.1.1)': dependencies: '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) @@ -16413,7 +16045,7 @@ snapshots: zod: 4.4.3 optionalDependencies: '@tanstack/react-router': 1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: - supports-color @@ -16633,22 +16265,31 @@ snapshots: '@tiptap/extensions': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) '@tiptap/pm': 3.31.3 - '@tsc-rs/darwin-arm64@0.7.0': + '@tsc-rs/darwin-arm64@0.8.0': optional: true - '@tsc-rs/darwin-x64@0.7.0': + '@tsc-rs/darwin-x64@0.8.0': optional: true - '@tsc-rs/linux-arm64@0.7.0': + '@tsc-rs/linux-arm64@0.8.0': optional: true - '@tsc-rs/linux-x64@0.7.0': + '@tsc-rs/linux-x64@0.8.0': optional: true - '@tsc-rs/win32-arm64@0.7.0': + '@tsc-rs/typescript@0.8.0': + optionalDependencies: + '@typescript/typescript-darwin-arm64': '@tsc-rs/darwin-arm64@0.8.0' + '@typescript/typescript-darwin-x64': '@tsc-rs/darwin-x64@0.8.0' + '@typescript/typescript-linux-arm64': '@tsc-rs/linux-arm64@0.8.0' + '@typescript/typescript-linux-x64': '@tsc-rs/linux-x64@0.8.0' + '@typescript/typescript-win32-arm64': '@tsc-rs/win32-arm64@0.8.0' + '@typescript/typescript-win32-x64': '@tsc-rs/win32-x64@0.8.0' + + '@tsc-rs/win32-arm64@0.8.0': optional: true - '@tsc-rs/win32-x64@0.7.0': + '@tsc-rs/win32-x64@0.8.0': optional: true '@tweenjs/tween.js@23.1.3': {} @@ -16812,9 +16453,6 @@ snapshots: '@types/stats.js@0.17.4': {} - '@types/statuses@2.0.6': - optional: true - '@types/three@0.180.0': dependencies: '@dimforge/rapier3d-compat': 0.12.0 @@ -16858,66 +16496,6 @@ snapshots: dependencies: '@types/node': 24.12.4 - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - '@ungap/structured-clone@1.3.1': {} '@vercel/config@0.3.0': @@ -16933,66 +16511,146 @@ snapshots: optionalDependencies: ajv: 6.15.0 - '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0)': + '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0)': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' optionalDependencies: babel-plugin-react-compiler: 1.0.0 oxc-transform-react: 0.152.0 - '@vitest/browser-playwright@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5)': + '@vitest/browser-playwright@5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0)': dependencies: - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/browser': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) + '@vitest/mocker': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) playwright: 1.60.0 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5)': + '@vitest/browser-preview@5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0)': dependencies: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) - vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/browser': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) + vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5)': + '@vitest/browser@5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) '@vitest/ui': 5.0.5(vitest@5.0.5) '@vitest/utils': 5.0.5 magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 - optionalDependencies: - msw: 2.12.11(@types/node@24.12.4)(typescript@7.0.2) - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml '@vitest/pretty-format@5.0.5': dependencies: @@ -17015,7 +16673,7 @@ snapshots: pathe: 2.0.3 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) '@vitest/utils@5.0.5': dependencies: @@ -17023,7 +16681,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.2.0 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@6.0.3))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)': + '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)': dependencies: '@oxc-project/runtime': 0.154.0 '@oxc-project/types': 0.154.0 @@ -17040,16 +16698,16 @@ snapshots: '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 - '@volar/typescript': 2.4.28(typescript@6.0.3) + '@volar/typescript': 2.4.28(@tsc-rs/typescript@0.8.0) esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 terser: 5.48.0 - typescript: 6.0.3 + typescript: '@tsc-rs/typescript@0.8.0' unrun: 0.2.39 yaml: 2.9.0 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)': + '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@6.0.3))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)': dependencies: '@oxc-project/runtime': 0.154.0 '@oxc-project/types': 0.154.0 @@ -17066,12 +16724,12 @@ snapshots: '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 - '@volar/typescript': 2.4.28(typescript@7.0.2) + '@volar/typescript': 2.4.28(typescript@6.0.3) esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 terser: 5.48.0 - typescript: 7.0.2 + typescript: 6.0.3 unrun: 0.2.39 yaml: 2.9.0 @@ -17137,22 +16795,22 @@ snapshots: '@volar/source-map@2.4.28': {} - '@volar/typescript@2.4.28(typescript@6.0.3)': + '@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0)': dependencies: '@volar/language-core': 2.4.28 path-browserify: 1.0.1 vscode-uri: 3.1.0 optionalDependencies: - typescript: 6.0.3 + typescript: '@tsc-rs/typescript@0.8.0' + optional: true - '@volar/typescript@2.4.28(typescript@7.0.2)': + '@volar/typescript@2.4.28(typescript@6.0.3)': dependencies: '@volar/language-core': 2.4.28 path-browserify: 1.0.1 vscode-uri: 3.1.0 optionalDependencies: - typescript: 7.0.2 - optional: true + typescript: 6.0.3 '@vscode/emmet-helper@2.11.0': dependencies: @@ -17310,9 +16968,9 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alchemy@2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(c76a5fb0ee7617c9ee4d0d91c51d5e28): + alchemy@2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(2676e7bf1eae706a0c10853e80d0a1b0): dependencies: - '@alchemy.run/cloudflare-runtime': 2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2) + '@alchemy.run/cloudflare-runtime': 2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5) '@alchemy.run/floci': 2.0.0-beta.80(effect@4.0.5) '@alchemy.run/node-utils': 2.0.0-beta.80 '@alchemy.run/sigil': 0.1.0-alpha.1(@types/react@19.3.0)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6)) @@ -17351,12 +17009,12 @@ snapshots: optionalDependencies: '@effect/platform-node': 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/sql-pg': 4.0.5(effect@4.0.5) - '@effect/vitest': 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@effect/vitest': 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) drizzle-kit: 1.0.0-rc.5-ab785fc - drizzle-orm: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(valibot@1.2.0(typescript@7.0.2))(zod@4.4.3) + drizzle-orm: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(zod@4.4.3) mongodb: 6.21.0(@aws-sdk/credential-providers@3.1062.0)(socks@2.8.9) pg: 8.23.0 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - '@types/node' @@ -17691,7 +17349,7 @@ snapshots: react-refresh: 0.14.2 optionalDependencies: '@babel/runtime': 7.29.7 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-widgets: 58.0.20(patch_hash=8bf49067f93742855e15f29559ac75c8b50230b47069060993c9ea01dac11171)(@babel/core@7.29.7(supports-color@8.1.1))(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) transitivePeerDependencies: - '@babel/core' @@ -17871,9 +17529,9 @@ snapshots: caniuse-lite@1.0.30001810: {} - capnp-es@0.0.16(typescript@7.0.2): + capnp-es@0.0.16(@tsc-rs/typescript@0.8.0): optionalDependencies: - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' capnweb@0.12.0: {} @@ -17936,9 +17594,6 @@ snapshots: cli-spinners@3.4.0: {} - cli-width@4.1.0: - optional: true - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -18072,9 +17727,6 @@ snapshots: cookie@0.7.2: {} - cookie@1.1.1: - optional: true - cookie@2.0.1: {} core-js-compat@3.49.0: @@ -18325,7 +17977,7 @@ snapshots: get-tsconfig: 4.14.3 jiti: 2.7.0 - drizzle-orm@1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(valibot@1.2.0(typescript@7.0.2))(zod@4.4.3): + drizzle-orm@1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(zod@4.4.3): optionalDependencies: '@cloudflare/workers-types': 4.20260604.1 '@effect/sql-d1': 4.0.0(effect@4.0.5) @@ -18338,7 +17990,6 @@ snapshots: effect: 4.0.5 expo-sqlite: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) pg: 8.23.0 - valibot: 1.2.0(typescript@7.0.2) zod: 4.4.3 dset@3.1.4: {} @@ -18422,12 +18073,12 @@ snapshots: transitivePeerDependencies: - supports-color - electron-webauthn@1.3.1(typescript@7.0.2): + electron-webauthn@1.3.1(@tsc-rs/typescript@0.8.0): dependencies: '@electron-webauthn/types': 1.3.1 - typescript: 7.0.2 + typescript: '@tsc-rs/typescript@0.8.0' optionalDependencies: - '@electron-webauthn/macos': 1.3.1(typescript@7.0.2) + '@electron-webauthn/macos': 1.3.1(@tsc-rs/typescript@0.8.0) electron-winstaller@5.4.0(supports-color@8.1.1): dependencies: @@ -18614,32 +18265,32 @@ snapshots: expo-application@58.1.2(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) - expo-asset@58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2): + expo-asset@58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: - '@expo/image-utils': 0.12.5(supports-color@8.1.1)(typescript@7.0.2) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + '@expo/image-utils': 0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: - supports-color - typescript - expo-audio@58.0.9(patch_hash=2c2f8c73a23d48804c8b537b8cd73c25eb331aa47d82eaa481c6766cb81e5add)(expo-asset@58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): + expo-audio@58.0.9(patch_hash=2c2f8c73a23d48804c8b537b8cd73c25eb331aa47d82eaa481c6766cb81e5add)(expo-asset@58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) - expo-asset: 58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) + expo-asset: 58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - expo-auth-session@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2): + expo-auth-session@58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: expo-application: 58.1.2(expo@58.0.14) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) expo-crypto: 58.0.7(expo@58.0.14) - expo-linking: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) + expo-linking: 58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) expo-web-browser: 58.0.6(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) invariant: 2.2.4 react: 19.3.0 @@ -18651,21 +18302,21 @@ snapshots: expo-blur@58.0.4(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-build-properties@58.0.11(expo@58.0.14): dependencies: '@expo/schema-utils': 58.0.2 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) resolve-from: 5.0.0 semver: 7.8.5 expo-camera@58.0.10(@types/emscripten@1.41.5)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: barcode-detector: 3.2.0(@types/emscripten@1.41.5) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: @@ -18673,15 +18324,15 @@ snapshots: expo-clipboard@58.0.3(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - expo-constants@58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2): + expo-constants@58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1): dependencies: '@expo/env': 2.5.2(supports-color@8.1.1) - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: - supports-color @@ -18689,11 +18340,11 @@ snapshots: expo-crypto@58.0.7(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-dev-client@58.0.14(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-dev-launcher: 58.0.14(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) expo-dev-menu: 58.0.11(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) expo-dev-menu-interface: 58.0.1(expo@58.0.14) @@ -18705,72 +18356,72 @@ snapshots: expo-dev-launcher@58.0.14(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: '@expo/schema-utils': 58.0.2 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-dev-menu: 58.0.11(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) expo-manifests: 58.0.1(expo@58.0.14) react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-dev-menu-interface@58.0.1(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-dev-menu@58.0.11(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-dev-menu-interface: 58.0.1(expo@58.0.14) react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-device@58.0.5(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) ua-parser-js: 0.7.41 expo-document-picker@58.0.4(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-eas-client@58.0.2: {} expo-file-system@58.0.9(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-glass-effect@58.0.4(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-haptics@58.0.7(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-image-loader@58.0.3(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-image-manipulator@58.0.14(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-image-loader: 58.0.3(expo@58.0.14) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-image-picker@58.0.10(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-image-loader: 58.0.3(expo@58.0.14) expo-image@58.0.15(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) sf-symbols-typescript: 2.2.0 @@ -18779,13 +18430,13 @@ snapshots: expo-keep-awake@58.0.3(expo@58.0.14)(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 - expo-linking@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2): + expo-linking@58.0.13(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) invariant: 2.2.4 react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -18795,13 +18446,13 @@ snapshots: expo-manifests@58.0.1(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-json-utils: 58.0.1 - expo-modules-autolinking@58.0.13(supports-color@8.1.1)(typescript@7.0.2): + expo-modules-autolinking@58.0.13(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1): dependencies: '@expo/platforms': 58.0.1 - '@expo/require-utils': 58.0.4(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 58.0.4(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 commander: 7.2.0 @@ -18830,17 +18481,17 @@ snapshots: expo-network@58.0.3(patch_hash=cd09b6d1744dde4efa081b5619ebcff38877d98f7502a51cab121629dece8237)(expo@58.0.14)(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 - expo-notifications@58.1.5(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2): + expo-notifications@58.1.5(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: - '@expo/image-utils': 0.12.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/image-utils': 0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) abort-controller: 3.0.0 badgin: 1.2.3 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-application: 58.1.2(expo@58.0.14) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: @@ -18849,14 +18500,14 @@ snapshots: expo-paste-input@0.1.15(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - expo-quick-actions@6.0.2(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2): + expo-quick-actions@6.0.2(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(supports-color@8.1.1): dependencies: - '@expo/image-utils': 0.8.14(supports-color@8.1.1)(typescript@7.0.2) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + '@expo/image-utils': 0.8.14(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) schema-utils: 4.3.3 sf-symbols-typescript: 2.2.0 transitivePeerDependencies: @@ -18865,33 +18516,33 @@ snapshots: expo-secure-store@58.0.2(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-sensors@58.0.4(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) invariant: 2.2.4 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-server@58.0.5: {} - expo-sharing@58.0.19(patch_hash=61a9741dc6166fdbb5baa2e489275a65dd21aa11d2c915f490a1b337a4468e76)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2): + expo-sharing@58.0.19(patch_hash=61a9741dc6166fdbb5baa2e489275a65dd21aa11d2c915f490a1b337a4468e76)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/config-types': 58.0.3 '@expo/plist': 0.10.1 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: - supports-color - typescript - expo-splash-screen@58.0.10(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2): + expo-splash-screen@58.0.10(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(supports-color@8.1.1): dependencies: - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) - '@expo/image-utils': 0.12.5(supports-color@8.1.1)(typescript@7.0.2) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/image-utils': 0.12.5(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) xml2js: 0.6.0 transitivePeerDependencies: - supports-color @@ -18900,7 +18551,7 @@ snapshots: expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: await-lock: 2.2.2 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -18909,7 +18560,7 @@ snapshots: expo-symbols@58.0.4(expo-font@58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: '@expo-google-fonts/material-symbols': 0.4.38 - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-font: 58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -18917,7 +18568,7 @@ snapshots: expo-updates-interface@58.0.1(expo@58.0.14): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-updates@58.0.17(expo-dev-client@58.0.14(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)))(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1): dependencies: @@ -18929,7 +18580,7 @@ snapshots: arg: 4.1.3 chalk: 4.1.2 debug: 4.4.3(supports-color@8.1.1) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) expo-eas-client: 58.0.2 expo-manifests: 58.0.1(expo@58.0.14) expo-structured-headers: 58.0.1 @@ -18948,20 +18599,20 @@ snapshots: expo-video@58.0.11(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) expo-web-browser@58.0.6(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)): dependencies: - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) ? expo-widgets@58.0.20(patch_hash=8bf49067f93742855e15f29559ac75c8b50230b47069060993c9ea01dac11171)(@babel/core@7.29.7(supports-color@8.1.1))(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) : dependencies: '@expo/plist': 0.10.1 '@expo/ui': 58.0.20(patch_hash=5a26480373850c1a7943919aa21f904151b335a0cb01042109e0746def37923e)(@babel/core@7.29.7(supports-color@8.1.1))(expo@58.0.14)(react-dom@19.3.0(react@19.3.0))(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) - expo: 58.0.14(f758e93be082e96fe84cde9538c0a615) + expo: 58.0.14(277a83826bb26ba15256633fefeb0bc2) react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) transitivePeerDependencies: @@ -18969,28 +18620,28 @@ snapshots: - react-dom - react-native-worklets - expo@58.0.14(f758e93be082e96fe84cde9538c0a615): + expo@58.0.14(277a83826bb26ba15256633fefeb0bc2): dependencies: '@babel/runtime': 7.29.7 - '@expo/cli': 58.1.11(122bb879cf862e703fa54111e57fbca5) - '@expo/config': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) - '@expo/config-plugins': 58.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/cli': 58.1.11(5e998cea2b9eedfd9ad81915214fc418) + '@expo/config': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) + '@expo/config-plugins': 58.0.9(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/devtools': 58.0.2(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@expo/dom-webview': 58.0.2(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@expo/fingerprint': 0.21.5(patch_hash=8fba80d6bbce46c72de427fe8c7907462d262a0bfe50c6b2b7ee9c4e0c61e5e1)(supports-color@8.1.1) - '@expo/local-build-cache-provider': 58.0.6(supports-color@8.1.1)(typescript@7.0.2) + '@expo/local-build-cache-provider': 58.0.6(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) '@expo/log-box': 58.0.12(@expo/dom-webview@58.0.2)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) '@expo/log-box-utils': 58.0.2 '@expo/metro': 58.0.0(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6) '@ungap/structured-clone': 1.3.1 babel-preset-expo: 58.0.17(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo-widgets@58.0.20)(expo@58.0.14)(react-refresh@0.14.2)(supports-color@8.1.1) - expo-asset: 58.0.16(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1)(typescript@7.0.2) - expo-constants: 58.0.12(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1)(typescript@7.0.2) + expo-asset: 58.0.16(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) + expo-constants: 58.0.12(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(supports-color@8.1.1) expo-file-system: 58.0.9(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6)) expo-font: 58.0.8(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) expo-keep-awake: 58.0.3(expo@58.0.14)(react@19.3.0) - expo-modules-autolinking: 58.0.13(supports-color@8.1.1)(typescript@7.0.2) + expo-modules-autolinking: 58.0.13(@tsc-rs/typescript@0.8.0)(supports-color@8.1.1) expo-modules-core: 58.0.22(patch_hash=f0b0fd100550ed76e5c25b4d13eb7c9ff62825f8f197c53edf7bf447c50c9fb7)(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) pretty-format: 29.7.0 react: 19.3.0 @@ -19475,9 +19126,6 @@ snapshots: property-information: 7.2.0 space-separated-tokens: 2.0.2 - headers-polyfill@4.0.3: - optional: true - heic-to@1.5.2: {} hermes-compiler@260318099.0.4: {} @@ -19620,9 +19268,6 @@ snapshots: is-interactive@2.0.0: {} - is-node-process@1.2.0: - optional: true - is-number@7.0.0: {} is-obj@2.0.0: {} @@ -20657,39 +20302,10 @@ snapshots: ms@2.1.3: {} - msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2): - dependencies: - '@inquirer/confirm': 5.1.21(@types/node@24.12.4) - '@mswjs/interceptors': 0.41.9 - '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.6 - cookie: 1.1.1 - graphql: 16.14.1 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - rettime: 0.10.1 - statuses: 2.0.2 - strict-event-emitter: 0.5.1 - tough-cookie: 6.0.2 - type-fest: 5.7.0 - until-async: 3.0.2 - yargs: 17.7.2 - optionalDependencies: - typescript: 7.0.2 - transitivePeerDependencies: - - '@types/node' - optional: true - muggle-string@0.4.1: {} multitars@1.0.2: {} - mute-stream@2.0.0: - optional: true - nanoid@3.3.19: {} nanoid@3.3.20: {} @@ -20918,9 +20534,6 @@ snapshots: orderedmap@2.1.1: {} - outvariant@1.4.3: - optional: true - oxc-parser@0.147.0: dependencies: '@oxc-project/types': 0.147.0 @@ -21014,7 +20627,7 @@ snapshots: '@oxc-transform-react/binding-win32-ia32-msvc': 0.152.0 '@oxc-transform-react/binding-win32-x64-msvc': 0.152.0 - oxfmt@0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + oxfmt@0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -21038,7 +20651,7 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.73.0 '@oxfmt/binding-win32-ia32-msvc': 0.73.0 '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) oxlint-tsgolint@7.0.2004: optionalDependencies: @@ -21049,7 +20662,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2004 '@oxlint-tsgolint/win32-x64': 7.0.2004 - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.88.0 '@oxlint/binding-android-arm64': 1.88.0 @@ -21071,7 +20684,7 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.88.0 '@oxlint/binding-win32-x64-msvc': 1.88.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) p-cancelable@2.1.1: {} @@ -21544,12 +21157,12 @@ snapshots: react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - react-native-keyboard-controller@1.22.7(patch_hash=f28d08f1233e3cce8b9324b4357d2b10f1ee040b8051bc87235b0fb95069b9bb)(312dd38e5b3f33a4cd42485c56107a1a): + react-native-keyboard-controller@1.22.7(patch_hash=f28d08f1233e3cce8b9324b4357d2b10f1ee040b8051bc87235b0fb95069b9bb)(e2a826d844fe10cdcbda558127861778): dependencies: react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) react-native-is-edge-to-edge: 1.3.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) - react-native-reanimated: 4.7.1(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) + react-native-reanimated: 4.7.1(patch_hash=95ee26e5fcab5f50bc4b4c47b3f7e6278a0b20850205efb9f6ee7e70a22d2b59)(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-nitro-modules@0.37.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: @@ -21566,7 +21179,7 @@ snapshots: react-native-blob-util: 0.25.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) react-native-nitro-modules: 0.37.1(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) - react-native-reanimated@4.7.1(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): + react-native-reanimated@4.7.1(patch_hash=95ee26e5fcab5f50bc4b4c47b3f7e6278a0b20850205efb9f6ee7e70a22d2b59)(react-native-worklets@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) @@ -21579,7 +21192,7 @@ snapshots: react: 19.3.0 react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) - react-native-screens@file:apps/mobile/deps/react-native-screens-5.0.0-t3.30.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): + react-native-screens@file:apps/mobile/deps/react-native-screens-5.0.0-t3.36.tgz(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0): dependencies: react: 19.3.0 react-freeze: 1.0.4(react@19.3.0) @@ -21867,9 +21480,6 @@ snapshots: retry@0.12.0: {} - rettime@0.10.1: - optional: true - rimraf@2.6.3: dependencies: glob: 7.2.3 @@ -22280,9 +21890,6 @@ snapshots: dependencies: stream-chain: 4.2.5 - strict-event-emitter@0.5.1: - optional: true - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -22509,15 +22116,6 @@ snapshots: ts-algebra@2.0.0: {} - tsc-rs@0.7.0: - optionalDependencies: - '@tsc-rs/darwin-arm64': 0.7.0 - '@tsc-rs/darwin-x64': 0.7.0 - '@tsc-rs/linux-arm64': 0.7.0 - '@tsc-rs/linux-x64': 0.7.0 - '@tsc-rs/win32-arm64': 0.7.0 - '@tsc-rs/win32-x64': 0.7.0 - tslib@2.8.1: {} type-fest@0.13.1: @@ -22547,29 +22145,6 @@ snapshots: typescript@6.0.3: {} - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 - ua-parser-js@0.7.41: {} ufo@1.6.4: {} @@ -22656,7 +22231,7 @@ snapshots: universalify@2.0.1: {} - uniwind@1.13.0(@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6))(metro-cache@0.87.1(supports-color@8.1.1))(metro-transform-worker@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(tailwindcss@4.3.3): + uniwind@1.13.0(@expo/metro-config@58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro-cache@0.87.1(supports-color@8.1.1))(metro-transform-worker@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(metro@0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(tailwindcss@4.3.3): dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 @@ -22668,7 +22243,7 @@ snapshots: react-native: 0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) tailwindcss: 4.3.3 optionalDependencies: - '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@6.0.6) + '@expo/metro-config': 58.0.14(patch_hash=90fa980e8b6480631fb2fbb04559834e3c612db40c550cd828f11be297d8d760)(@tsc-rs/typescript@0.8.0)(bufferutil@4.1.0)(expo@58.0.14)(supports-color@8.1.1)(utf-8-validate@6.0.6) metro-transform-worker: 0.87.1(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6) unpipe@1.0.0: {} @@ -22699,9 +22274,6 @@ snapshots: idb-keyval: 6.2.1 ioredis: 5.11.0(supports-color@8.1.1) - until-async@3.0.2: - optional: true - unzipper@0.12.5: dependencies: bluebird: 3.7.2 @@ -22742,11 +22314,6 @@ snapshots: uuid@7.0.3: {} - valibot@1.2.0(typescript@7.0.2): - optionalDependencies: - typescript: 7.0.2 - optional: true - validate-npm-package-name@5.0.1: {} vary@1.1.2: {} @@ -22768,24 +22335,24 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): + vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): dependencies: '@oxc-project/types': 0.154.0 '@oxlint/plugins': 1.88.0 - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/browser': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) + '@vitest/browser-preview': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) + '@vitest/mocker': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) '@vitest/pretty-format': 5.0.5 '@vitest/snapshot': 5.0.5 '@vitest/spy': 5.0.5 '@vitest/utils': 5.0.5 - oxfmt: 0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(typescript@7.0.2))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxfmt: 0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) oxlint-tsgolint: 7.0.2004 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' - vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' + vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) optionalDependencies: - '@vitest/browser-playwright': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/browser-playwright': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 @@ -22856,10 +22423,10 @@ snapshots: - vue-tsc - yaml - vitest@5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)): + vitest@5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -22870,16 +22437,36 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.12.4 - '@vitest/browser-playwright': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28(typescript@7.0.2))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/browser-playwright': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) + '@vitest/browser-preview': 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(vitest@5.0.5)(yaml@2.9.0) '@vitest/ui': 5.0.5(vitest@5.0.5) jsdom: 30.1.0(@noble/hashes@1.8.0) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml vlq@1.0.1: {} @@ -23074,13 +22661,6 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260918.1 '@cloudflare/workerd-windows-64': 1.20260918.1 - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - optional: true - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -23191,9 +22771,6 @@ snapshots: yocto-queue@1.2.2: {} - yoctocolors-cjs@2.1.3: - optional: true - yoctocolors@2.1.2: {} yuku-core@0.19.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2bc5c47593203..21ad09c292f0e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -24,7 +24,7 @@ allowBuilds: objc-js: false "react-native-enriched-markdown@file:apps/mobile/deps/react-native-enriched-markdown-1.2.0-t3.15-fee84114.tgz": true sharp: true - tsc-rs: true + "@tsc-rs/typescript": true utf-8-validate: false workerd: false @@ -46,7 +46,6 @@ catalog: "@effect/platform-node-shared": 4.0.5 "@effect/sql-pg": 4.0.5 "@effect/sql-sqlite-do": 4.0.5 - "@effect/tsgo": 0.51.1 "@effect/vitest": 4.0.5 "@electron-webauthn/macos": 1.3.1 "@legendapp/list": 3.3.5 @@ -71,7 +70,7 @@ catalog: react-dom: 19.3.0 tailwindcss: 4.3.3 tldts: 7.4.2 - typescript: 7.0.2 + typescript: npm:@tsc-rs/typescript@0.8.0 undici: 8.11.2 vite: npm:@voidzero-dev/vite-plus-core@1.3.0 vite-plus: 1.3.0 @@ -92,7 +91,6 @@ catalog: unified: 11.0.5 minimumReleaseAgeExclude: - - tsc-rs - "@tsc-rs/*" - expo-glass-effect@58.0.4 - expo-blur@58.0.4 @@ -349,6 +347,9 @@ patchedDependencies: dbus-next@0.10.2: patches/dbus-next@0.10.2.patch node-pty@1.2.0-beta.15: patches/node-pty@1.2.0-beta.15.patch react-native-gesture-handler@3.3.0: patches/react-native-gesture-handler@3.3.0.patch + # Screens v5 stack, split and tab screens leave at once, like legacy screens. Exiting animations + # inside one otherwise hold its removal for a later mount, after its stack has moved on. + react-native-reanimated@4.7.1: patches/react-native-reanimated@4.7.1.patch react-native-keyboard-controller@1.22.7: patches/react-native-keyboard-controller@1.22.7.patch react-native-pdf-jsi@5.0.0: patches/react-native-pdf-jsi@5.0.0.patch # Android: a nested WebView hands a vertical drag to its scrolling parent at the page's edge. @@ -364,6 +365,7 @@ patchedDependencies: peerDependencyRules: allowAny: + - typescript - vite allowedVersions: vite: "*" diff --git a/scripts/package.json b/scripts/package.json index 29029cef9d3e3..a4aa8c1496267 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "typecheck": "tsc-rs --noEmit", + "typecheck": "tsc --noEmit", "test": "vp test run --config ../vite.config.ts --dir ." }, "dependencies": {