Repository navigation
Fix Hive viewer lifecycle, replay identity, and shared-package integration #8027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
austinywang
wants to merge
118
commits into
main
Choose a base branch
from
issue-8001-hive-viewer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
118 commits
Select commit
Hold shift + click to select a range
6e35594
hive M1: Settings > Computers on macOS with presence and Mac-to-Mac p…
austinywang ef3bdc5
Declare CmuxMobileShell and CmuxMobilePairedMac products on the app t…
austinywang c0db87b
Revert "Declare CmuxMobileShell and CmuxMobilePairedMac products on t…
austinywang cb93c83
Compose the computers directory inside CmuxHive so the app links only…
austinywang 77a7cd4
Link CmuxAgentChat into cmuxTests directly
austinywang 72450f9
Make the composition's retry-delay closure explicitly @Sendable
austinywang 9ab3b36
Computers: copy this Mac's pairing link for Mac-to-Mac pairing
austinywang 003e87e
Computers: drop the iPhone QR button from Pair This Mac
austinywang e144821
Computers: bordered style for Copy Pairing Link
austinywang d2349b5
hive M1: remote-Mac viewer — live workspaces, terminal, and input ove…
austinywang aa4f537
Fix viewer-branch CI: repair pbxproj merge, follow aux-window lint, d…
austinywang aa5b65b
Make viewer retry-delay closures explicitly @Sendable
austinywang 136c70d
Computers: bordered style for the row Open button
austinywang 03978d7
Move Mac-consumed mobile packages from Packages/iOS to Packages/Shared
austinywang 7fd3089
Computers: replace pairing link with a 6-digit code, drop Refresh List
austinywang a2e1bc7
web: port React-19 script-tag patch to @stackframe/stack 2.8.108, pat…
austinywang ef575db
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 232893b
Repair pbxproj merge resolution: restore isa/closing braces on packag…
austinywang f8140db
Post-merge fixes: dedupe searchText into its extension file, restore …
austinywang 3279466
Hive viewer fixes from dogfood: block self-pairing, fix span width re…
austinywang c5f54d4
Hive viewer: trust the Tailscale tunnel for Stack auth (Mac viewer only)
austinywang 30220ad
Computers: configurable dual view — per-computer windows or main-side…
austinywang 889f2d2
Trust Tailscale's IPv6 ULA range in the Mac viewer's Stack-auth chann…
austinywang 82c831b
Hive viewer: dedicated tailnet-verified Tailscale transport factory
austinywang 88c30c7
Hive: native mirror workspaces — remote Mac terminals in the real cmu…
austinywang b66f649
Hive mirrors: live topology reconcile, remote-grid sizing, computer s…
austinywang 8c2c5a1
Hive: per-window computer scope; windows mode opens a native cmux window
austinywang 8b07518
Hive mirrors: repaint from cached full frame on selection and size ev…
austinywang bfe96a6
Hive mirrors: font-fitted scale-to-fill for mirror surfaces
austinywang 056aa6f
Host: boot never-realized surfaces headless on mobile.terminal.replay
austinywang f1c7d93
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 74348b2
Hive mirrors: scope selection repaints, prune dead terminals, revert …
austinywang 799cf86
Hive mirrors: unwedge never-focused renderers so mirror panes paint
austinywang 90ff3a5
Hive mirrors: show connection status instead of a blank sidebar
austinywang 2d610eb
Hive viewer: close lifecycle and rendering review gaps
austinywang a7d022c
Hive mirrors: reset scope during teardown
austinywang 294e7d9
Hive directory: avoid idle presence subscriptions
austinywang 61c025b
Hive viewer: enforce pairing and recovery boundaries
austinywang 3784099
Hive viewer: require verified host identity
austinywang 12d61d0
Hive viewer: reconcile session routes and mirror titles
austinywang 2648850
Hive viewer: add grid and event-router regression tests
austinywang 9409643
Hive viewer: cover unverified Tailscale bearer rejection
austinywang 9c4d29d
Hive viewer: bind render streams and fail closed on Tailscale
austinywang 2f93d45
Hive directory: cover unpair sign-out race
austinywang 03e35f1
Hive viewer: preserve scope across safe teardown
austinywang 10ba591
Hive viewer: return the guarded Tailscale transport
austinywang 1f3fee4
Fix Hive viewer auth and lifecycle recovery
austinywang 2ff8667
Move pairing code formatter helpers to file scope
austinywang 8b058f0
Test Tailscale peer admission boundaries
austinywang 941f6aa
Bind Hive viewers to verified Tailscale peers
austinywang f5b59bc
Wire Hive route admission into the app target
austinywang ca9b868
Close Hive pairing and sidebar failure gaps
austinywang c875524
Repair Hive mirror retry and close cleanup
austinywang 473aa2d
Keep legacy Tailscale bearer transport fail closed
austinywang 36dacdb
Revoke Hive sessions on every sign-out path
austinywang 0c3cf10
Scope Hive pairing to owned supported hosts
austinywang 0b2e393
Propagate registry ownership into Hive rows
austinywang 732dad9
Fix Hive identity and render update races
austinywang 5901929
Gate Hive actions until transport admission
austinywang cfd86cd
Finish Hive gate and fallback correctness
austinywang 06859f3
Finish Hive scoped updates and transport gating
austinywang 5a1fffe
Harden Hive scope and replay recovery
austinywang 64941da
Close Hive replay and subscription races
austinywang 64e1655
Serialize overlapping Hive replay attempts
austinywang 7ba6556
Merge origin/main into issue-8001-hive-viewer
austinywang 3314c0c
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang a1fce39
Keep Hive compatibility test fixture scoped
austinywang c23d208
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 5f5392f
test: cover retrying a failed Hive session
austinywang 3233011
fix: close Hive viewer review gaps
austinywang bb9dba6
fix: finish Hive review cleanup
austinywang 95a341c
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 4746e2e
ci: update moved RPC determinism allowlist path
austinywang e035d60
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 024754f
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 66f5abf
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 31bff49
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 1591a9c
fix: finish Hive package relocation and split viewer files
austinywang 72d1870
test: expose Hive snapshot event loss and bound async waits
austinywang def28b4
fix: preserve Hive events across snapshots and share host subscriptions
austinywang 49149f9
fix: cache Sendable Hive font measurements
austinywang 3693953
ci: run standalone Hive domain and AppKit package tests
austinywang f9ef065
test: expose stale Hive account admission and pairing reloads
austinywang 922ab0a
fix: reject stale Hive scope reads before publishing paired rows
austinywang 1845269
test: expose misrouted Hive replay envelope acceptance
austinywang 594fe68
fix: require matching Hive replay workspace and surface identities
austinywang 5bcb63c
ci: validate project syntax with the native macOS plist parser
austinywang 851afad
fix: repair Hive Xcode group closure and quote extension filename
austinywang 3131bf9
fix: handle clock errors in the Computers settings view task
austinywang 2640e09
fix: reconcile extracted Hive helpers with current main declarations
austinywang 58cd221
fix: use main canonical terminal startup for Hive replay
austinywang 4b4488d
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 4148a4a
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 2f8091f
fix: handle all clock errors while attaching Hive mirrors
austinywang c72c06b
test: expose Hive pairing, scope refresh, input recovery, and admissi…
austinywang 0eebc1f
fix: close Hive scope, refresh ownership, replay input, and admission…
austinywang 9260576
fix: initialize Hive sidebar snapshots outside ViewBuilder statements
austinywang a03fef5
test: wire the Hive scope into the sidebar scale harness
austinywang 278af10
fix: repair app-host test wiring and Release compilation
austinywang f6bb617
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 8bb5197
test: expose Hive UUID and Release route selection gaps
austinywang c8d136c
fix: unify Hive route policy and own mirror replacements
austinywang 258b0a6
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 6eb3532
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 64cf110
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang d45d727
test: expose overlapping Hive replay and disconnect admission races
austinywang af1f46b
fix: serialize Hive replay ownership and revoke disconnect admission
austinywang f96ff8f
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 30036f3
fix: start Hive transport shutdown before draining cancelled workers
austinywang 3de424c
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 5cc8e32
fix: serialize app-host tests and align Iroh lane expectations
austinywang 3673845
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 48d2706
Merge remote-tracking branch 'origin/main' into issue-8001-hive-viewer
austinywang 5e721aa
fix: use portable web script entrypoint guard
austinywang 6c1035d
Merge origin/main into issue-8001-hive-viewer
austinywang 6bdaa9e
fix: preserve managed app behavior and show Hive connection errors
austinywang fddc6af
fix: align Cloud client bootstrap with current API
austinywang cdd9bb5
fix: retain Hive input during attach and update determinism path
austinywang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxPairingCode.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| public import Foundation | ||
|
|
||
| /// A short-lived 6-digit Mac-to-Mac pairing code advertised through the team | ||
| /// device registry. | ||
| /// | ||
| /// The host Mac publishes the code (and its expiry) as instance labels on its | ||
| /// own `/api/devices` registration; another Mac claims it by typing the code, | ||
| /// matching it against the registry rows it can already see, and pairing with | ||
| /// the matching instance's advertised routes. The code is a rendezvous | ||
| /// selector, not a bearer secret: registry rows (including routes) are already | ||
| /// team-visible, and the pairing RPC still enforces account identity. | ||
| /// | ||
| /// ```swift | ||
| /// let minted = CmxPairingCode(code: "042117", expiresAt: expiry) | ||
| /// body["instanceLabels"] = minted.instanceLabels | ||
| /// // …on the claiming Mac: | ||
| /// let active = CmxPairingCode.active(in: instance.labels, now: Date()) | ||
| /// ``` | ||
| public struct CmxPairingCode: Equatable, Sendable { | ||
| /// Instance label carrying the 6-digit code. | ||
| public static let codeLabelKey = "pairing_code" | ||
| /// Instance label carrying the code's ISO 8601 expiry timestamp. | ||
| public static let expiresAtLabelKey = "pairing_code_expires_at" | ||
|
|
||
| /// The 6-digit, zero-padded code the user reads off the host Mac. | ||
| public var code: String | ||
| /// When the code stops being claimable. | ||
| public var expiresAt: Date | ||
|
|
||
| /// Creates a pairing code value. | ||
| /// - Parameters: | ||
| /// - code: The 6-digit, zero-padded code string. | ||
| /// - expiresAt: When the code stops being claimable. | ||
| public init(code: String, expiresAt: Date) { | ||
| self.code = code | ||
| self.expiresAt = expiresAt | ||
| } | ||
|
|
||
| /// Generates a fresh 6-digit code (zero-padded, `000000`–`999999`) | ||
| /// expiring `ttl` from `now`. | ||
| /// | ||
| /// - Parameters: | ||
| /// - ttl: Code lifetime. | ||
| /// - now: Mint-side clock, injected for testability. | ||
| /// - generator: Randomness source, injected for testability. | ||
| /// - Returns: The minted code value. | ||
| // lint:allow namespace-type — this type owns code/expiry state; this is a value factory. | ||
| public static func minted( | ||
| ttl: TimeInterval, | ||
| now: Date, | ||
| using generator: inout some RandomNumberGenerator | ||
| ) -> CmxPairingCode { | ||
| CmxPairingCode( | ||
| code: String(format: "%06d", Int.random(in: 0...999_999, using: &generator)), | ||
| expiresAt: now.addingTimeInterval(ttl) | ||
| ) | ||
| } | ||
|
|
||
| /// The wire form: the instance labels a registration POST advertises. | ||
| public var instanceLabels: [String: String] { | ||
| [ | ||
| Self.codeLabelKey: code, | ||
| Self.expiresAtLabelKey: Self.iso8601.string(from: expiresAt), | ||
| ] | ||
| } | ||
|
|
||
| /// Decodes an unexpired pairing code from registry instance labels. | ||
| /// | ||
| /// A missing or unparseable expiry yields `nil` — a claim must never | ||
| /// succeed against a code whose lifetime cannot be verified. | ||
| /// | ||
| /// - Parameters: | ||
| /// - labels: The instance's string labels as returned by the registry. | ||
| /// - now: The claim-side clock, injected for testability. | ||
| /// - Returns: The active code, or `nil` when absent or expired. | ||
| // lint:allow namespace-type — this type owns code/expiry state; this decodes that value. | ||
| public static func active(in labels: [String: String], now: Date) -> CmxPairingCode? { | ||
| guard | ||
| let code = labels[codeLabelKey]?.trimmingCharacters(in: .whitespacesAndNewlines), | ||
| !code.isEmpty, | ||
| let rawExpiry = labels[expiresAtLabelKey], | ||
| let expiresAt = Self.parseISO8601(rawExpiry), | ||
| expiresAt > now | ||
| else { return nil } | ||
| return CmxPairingCode(code: code, expiresAt: expiresAt) | ||
| } | ||
|
|
||
| /// The user-typed claim input normalized for matching: digits only, so | ||
| /// `"042 117"` and `"042117"` claim the same code. | ||
| /// - Parameter rawInput: The text the user typed. | ||
| /// - Returns: The digit string, or `nil` unless exactly 6 digits remain. | ||
| // lint:allow namespace-type — normalization is part of this value's claim grammar. | ||
| public static func normalizedClaimInput(_ rawInput: String) -> String? { | ||
| let digits = String(rawInput.compactMap { character -> Character? in | ||
| guard character.unicodeScalars.count == 1, | ||
| let scalar = character.unicodeScalars.first, | ||
| (48...57).contains(scalar.value) else { | ||
| return nil | ||
| } | ||
| return character | ||
| }) | ||
| return digits.count == 6 ? digits : nil | ||
| } | ||
|
|
||
| /// Encoder for the expiry label; fractional seconds match the registry's | ||
| /// own `toISOString()` timestamps. | ||
| private static var iso8601: ISO8601DateFormatter { | ||
| let formatter = ISO8601DateFormatter() | ||
| formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] | ||
| return formatter | ||
| } | ||
|
|
||
| /// Lenient ISO 8601 parse (fractional and whole-second forms). | ||
| // lint:allow static-helper — formatter construction stays inside the value's | ||
| // Sendable boundary and is only used on registry label reads. | ||
| private static func parseISO8601(_ raw: String) -> Date? { | ||
| let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard !trimmed.isEmpty else { return nil } | ||
| let fractional = ISO8601DateFormatter() | ||
| fractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds] | ||
| if let date = fractional.date(from: trimmed) { return date } | ||
| let whole = ISO8601DateFormatter() | ||
| whole.formatOptions = [.withInternetDateTime] | ||
| return whole.date(from: trimmed) | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/CmxPairingCodeTests.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import Foundation | ||
| import Testing | ||
| @testable import CMUXMobileCore | ||
|
|
||
| /// Deterministic generator so minted codes are reproducible in tests. | ||
| private struct SeededGenerator: RandomNumberGenerator { | ||
| var state: UInt64 | ||
| mutating func next() -> UInt64 { | ||
| state = state &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407 | ||
| return state | ||
| } | ||
| } | ||
|
|
||
| struct CmxPairingCodeTests { | ||
| private let now = Date(timeIntervalSince1970: 1_000) | ||
|
|
||
| @Test func mintedProducesSixDigitZeroPaddedCodeAndTTLExpiry() { | ||
| var generator = SeededGenerator(state: 7) | ||
| let minted = CmxPairingCode.minted(ttl: 600, now: now, using: &generator) | ||
| #expect(minted.code.count == 6) | ||
| let allDigits = minted.code.allSatisfy(\.isNumber) | ||
| #expect(allDigits) | ||
| #expect(minted.expiresAt == now.addingTimeInterval(600)) | ||
| } | ||
|
|
||
| @Test func instanceLabelsRoundTripThroughActive() { | ||
| let minted = CmxPairingCode(code: "042117", expiresAt: now.addingTimeInterval(600)) | ||
| let decoded = CmxPairingCode.active(in: minted.instanceLabels, now: now) | ||
| #expect(decoded == minted) | ||
| } | ||
|
|
||
| @Test func activeRejectsExpiredMissingOrUnparseableExpiry() { | ||
| let expired = CmxPairingCode(code: "042117", expiresAt: now.addingTimeInterval(-1)) | ||
| #expect(CmxPairingCode.active(in: expired.instanceLabels, now: now) == nil) | ||
| #expect(CmxPairingCode.active( | ||
| in: [CmxPairingCode.codeLabelKey: "042117"], | ||
| now: now | ||
| ) == nil) | ||
| #expect(CmxPairingCode.active( | ||
| in: [ | ||
| CmxPairingCode.codeLabelKey: "042117", | ||
| CmxPairingCode.expiresAtLabelKey: "not-a-date", | ||
| ], | ||
| now: now | ||
| ) == nil) | ||
| #expect(CmxPairingCode.active(in: [:], now: now) == nil) | ||
| } | ||
|
|
||
| @Test func activeAcceptsWholeSecondExpiryForm() { | ||
| let labels = [ | ||
| CmxPairingCode.codeLabelKey: "042117", | ||
| CmxPairingCode.expiresAtLabelKey: "1970-01-01T00:33:20Z", | ||
| ] | ||
| let decoded = CmxPairingCode.active(in: labels, now: now) | ||
| #expect(decoded?.code == "042117") | ||
| #expect(decoded?.expiresAt == Date(timeIntervalSince1970: 2_000)) | ||
| } | ||
|
|
||
| @Test func normalizedClaimInputAcceptsExactlySixDigits() { | ||
| #expect(CmxPairingCode.normalizedClaimInput("042117") == "042117") | ||
| #expect(CmxPairingCode.normalizedClaimInput(" 042 117 ") == "042117") | ||
| #expect(CmxPairingCode.normalizedClaimInput("042-117") == "042117") | ||
| #expect(CmxPairingCode.normalizedClaimInput("42117") == nil) | ||
| #expect(CmxPairingCode.normalizedClaimInput("0421178") == nil) | ||
| #expect(CmxPairingCode.normalizedClaimInput("042117") == nil) | ||
| #expect(CmxPairingCode.normalizedClaimInput("") == nil) | ||
| } | ||
| } |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.