Skip to content

atualizando - #140

Open
felipersd8 wants to merge 56 commits into
evolution-foundation:mainfrom
felipersd8:main
Open

atualizando#140
felipersd8 wants to merge 56 commits into
evolution-foundation:mainfrom
felipersd8:main

Conversation

@felipersd8

@felipersd8 felipersd8 commented Jul 29, 2026

Copy link
Copy Markdown

Description

Related Issue

Closes #(issue_number)

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced

Screenshots (if applicable)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Any dependent changes have been merged and published

Additional Notes

Summary by Sourcery

Align API surface and documentation with typed response models, add advanced messaging/payment features, improve presence handling and persistence, and introduce observability and deployment assets.

New Features:

  • Add typed Swagger/OpenAPI response models across handlers and docs, replacing generic gin.H usage.
  • Introduce license management endpoints for registration, activation, and status checks.
  • Add PIX payment support via dedicated send APIs and native flow messages, including a new /send/pix route.
  • Expose server health and Prometheus-compatible metrics endpoints plus a lightweight HTML dashboard for monitoring.
  • Add new chat, group, message, poll, and user capabilities such as history-sync, group settings updates, audio mark-as-played, and extended event types.

Enhancements:

  • Refine button, list, and carousel message generation to use native interactive/ButtonsMessage structures with richer metadata and media support.
  • Rework presence update scheduling to respect AlwaysOnline, handle transient errors robustly, and restart when settings change.
  • Improve message persistence by centralizing async inserts, adding referral storage, and using upserts that preserve referral JSON.
  • Harden MinIO media storage initialization by factoring and testing bucket existence/creation logic.
  • Tighten instance status handling to avoid forcing connections when only status is requested and to better differentiate connected vs logged-in states.

Deployment:

  • Add a Docker stack file tailored for an Evolution GO deployment and update the Dockerfile to clone whatsmeow, ship the new dashboard, and better support cloud environments.

Documentation:

  • Update Swagger JSON/YAML and advanced docs to reflect new endpoints, response schemas, and referral payload behavior in events.

Tests:

  • Add focused unit tests for presence handling, MinIO bucket management, referral extraction, and message repository upsert behavior.
  • Add an optional integration test for MinIO-backed media storage bucket creation.

github-actions Bot and others added 30 commits March 25, 2026 16:15
Add centralized swagger models in pkg/core/swagger_models.go to provide consistent API response structures.
Update all handler Swagger annotations to reference these core models instead of generic gin.H responses.
This improves API documentation clarity and ensures consistent response formats across all endpoints.

Additionally, refactor version initialization in main.go to use a function and add WebSocket endpoint documentation.
Create the configured bucket when missing, matching fix/create-bucket-if-no-exist-minio.
Add unit tests (fake client) and optional integration test behind -tags=integration.

Made-with: Cursor
- Gate schedulePresenceUpdates goroutine behind AlwaysOnline flag
  (previously ran unconditionally for every instance)
- Refactor to extract handlePresenceTick as a pure, testable function
- Add PresenceSender interface to decouple from whatsmeow.Client
- Start presence goroutine dynamically when AlwaysOnline is toggled
  ON via the advanced-settings API without requiring reconnect
- Remove processPresenceUpdates (random unavailable/available cycle)
- Add unit tests covering all 4 tick scenarios
WhatsApp now uses LID (Linked ID) format for participant and owner JIDs
in groups. The previous implementation compared group.OwnerJID.User and
participant.JID.User against the client phone number, but both fields are
now LID values (e.g. 216857898393848@lid) rather than phone numbers,
causing the endpoint to always return an empty array.

Fix uses group.OwnerPN.User (phone-number JID of the owner, always
populated alongside the LID-based OwnerJID) and participant.PhoneNumber.User
(phone-number JID per participant) for the comparison. Also extends the
logic to include groups where the user is admin but not the creator.
Removes the TODO comment from the route since the endpoint now works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- handlePresenceTick: transient DB/network errors no longer kill the
  goroutine permanently; only ErrInstanceNotFound (record gone) triggers
  shouldStop=true so AlwaysOnline survives temporary backend issues

- instance_repository: add ErrInstanceNotFound sentinel and wrap
  gorm.ErrRecordNotFound so callers can distinguish not-found from
  transient failures without importing gorm directly

- schedulePresenceUpdates: accepts context.Context so callers can wire
  a cancellable/service-level context; adds ctx.Done() select case as
  an additional shutdown path alongside killChannel

- test mocks: unimplemented repository methods now panic instead of
  silently returning zero values, making accidental usage detectable;
  added TestHandlePresenceTick_TransientRepoError to cover the new
  retry-on-transient-error behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pre-allocate myGroups slice with len(resp) capacity to avoid repeated
  allocations when the user participates in many groups

- Guard all JID.User comparisons with myUser != "" to avoid false
  positives when the client's own JID is unexpectedly zero-valued;
  extract ownerPhone/ownerJID locals to reduce repeated field access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /manager dashboard previously showed only a static placeholder
("Dashboard content will be implemented here..."). This replaces it
with a standalone HTML page that fetches live data from the API and
displays real metrics:

- Total instances count
- Connected instances count and percentage
- Disconnected instances count
- Server health status (GET /server/ok)
- AlwaysOnline count
- Instance table with name, status badge, phone number, client and
  AlwaysOnline indicator
- Auto-refresh every 30 seconds with manual refresh button

Implementation uses a standalone HTML file (Tailwind CDN + vanilla JS
fetch) served at GET /manager, keeping the existing compiled bundle
intact for all other routes (/manager/instances, /manager/login, etc.).

Changes:
- manager/dashboard/index.html: new self-contained dashboard page
- pkg/routes/routes.go: serve dashboard/index.html for GET /manager
  (exact), keep dist/index.html for GET /manager/*any (wildcard)
- Dockerfile: copy manager/dashboard/ into the final image
- .gitignore: exclude manager build artifacts from version control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces innerHTML interpolation of server-supplied fields (name,
clientName, jid) with DOM createElement/textContent to eliminate
potential XSS.

Also detects a missing or empty API key before making any requests and
renders an actionable message explaining how to set it, instead of
showing a generic fetch error.
Removes the '// TODO: not working' markers from the six chat endpoints
(pin, unpin, archive, unarchive, mute, unmute). Investigation confirmed
the implementation is correct: the endpoints work on fully-established
sessions that have synced WhatsApp app state keys. The markers were
likely added after testing on a fresh session where keys had not yet
been distributed by the WhatsApp server.

Also fixes the hardcoded 1-hour mute duration: the BodyStruct now
accepts an optional `duration` field (seconds). Sending 0 or omitting
the field mutes the chat indefinitely, matching WhatsApp's own behaviour.
Reject negative duration values with a 400-level validation error.
Document that duration=0 maps to 'mute forever' (BuildMute treats 0
as a zero time.Duration, which causes BuildMuteAbs to set the
WhatsApp sentinel timestamp of -1).
Clamp duration to a maximum of 1 year (31536000 seconds) to avoid
unreasonably large timestamps being sent to the WhatsApp API.
Adds GET /metrics serving standard Prometheus text format.
No authentication required — follows the Prometheus convention of
protecting the endpoint at the network/ingress level.

Metrics exposed:

  evolution_instances_total               total registered instances (gauge)
  evolution_instances_connected           connected instances (gauge)
  evolution_instances_disconnected        disconnected instances (gauge)
  evolution_http_requests_total           HTTP requests by method/path/status (counter)
  evolution_http_request_duration_seconds HTTP latency by method/path (histogram)
  evolution_build_info                    always 1, version label carries the value (gauge)
  evolution_uptime_seconds                seconds since server start (gauge)

Instance gauges use a custom Collector that queries the database on
each scrape, so values are always current without event hooks.
HTTP path labels use Gin registered route patterns (e.g. /instance/:instanceId)
to keep cardinality bounded regardless of distinct IDs in the path.

New dependency: github.com/prometheus/client_golang v1.20.5
…volution-foundation#20

GET /instance/status was calling ensureClientConnected, which returns
an error when the WhatsApp client exists but is not connected (e.g.
after the user manually removes the device from their phone).
This caused the endpoint to return HTTP 400 until the container was
restarted, making it impossible for clients to detect the disconnected
state without restarting the server.

Status is a read-only query: it should report the current state, not
require an active connection to do so. The fix reads clientPointer
directly and returns Connected=false/LoggedIn=false when the client
is nil or disconnected, without attempting reconnection.

Fixes evolution-foundation#20
The Pair function was calling PairPhone directly without checking if
the client was initialized, and was silently swallowing errors from
PairPhone. This caused two problems:

1. If the client was nil or disconnected, PairPhone returned an error
   but the function ignored it and returned HTTP 200 with an empty
   PairingCode field, misleading the caller into thinking it succeeded.

2. The client was never started before PairPhone was called. WhatsApp
   requires the client to be connected to the WA websocket (waiting
   for auth) before a pairing code can be generated.

Fix:
- Start the instance automatically if no active connection exists,
  mirroring the QR code flow
- Wait 3 seconds for the WA websocket connection to establish and
  the initial QR generation to begin (required by whatsmeow before
  PairPhone can be called)
- Reject early if the instance is already authenticated
- Return PairPhone errors to the caller instead of swallowing them,
  so the handler correctly responds with HTTP 500 and an actionable
  error message

Fixes evolution-foundation#21
…rousel)

- Add SendPix function with full WhatsApp payment protocol support
  (AdditionalNodes biz/bot nodes, DeviceListMetadataVersion, NativeFlowMessage)
- Add /send/pix route and SendPix handler with input validation
- Rewrite SendButton: DocumentWithCaptionMessage wrapper + MessageSecret +
  AdditionalNodes biz nodes; supports reply, URL, call, copy and review_and_pay
- Rewrite SendList: DocumentWithCaptionMessage wrapper + MessageSecret +
  AdditionalNodes (product_list v=2 biz nodes)
- Fix SendCarousel: add MessageSecret (32 bytes), set HSCROLL_CARDS card type,
  add MessageParamsJSON+MessageVersion on card buttons, use json.Marshal instead
  of fmt.Sprintf, remove forced empty ContextInfo that broke iOS rendering,
  pass Quoted message, simplify image upload (remove thumbnail generation)
- Fix SendMessage central: add AdditionalNodes support in sendExtra, add
  ButtonsMessage and ListMessage wrapped cases in DocumentWithCaptionMessage,
  fix InteractiveMessage handling (skip empty ContextInfo on direct carousel)
- Remove unused image/jpeg import
… from @paluan-batista

- Move GinMiddleware registration to after CORS so OPTIONS requests
  aborted by CORS policy are not recorded as application metrics
- Add 30s TTL cache to instanceCollector.Collect to avoid a DB query
  on every Prometheus scrape, reducing load spikes at scale
- Improve mute duration error message to explicitly say "mute duration"
  so the caller understands why the request was rejected

Thanks @paluan-batista for the thorough review!
… from @paluan-batista

- Move GinMiddleware registration to after CORS so OPTIONS requests
  aborted by CORS policy are not recorded as application metrics
- Add 30s TTL cache to instanceCollector.Collect to avoid a DB query
  on every Prometheus scrape, reducing load spikes at scale
- Improve mute duration error message to explicitly say "mute duration"
  so the caller understands why the request was rejected

Thanks @paluan-batista for the thorough review!
Adds a new POST /message/markplayed endpoint that sends a 'played'
receipt (whatsmeow types.ReceiptTypePlayed), making the microphone
icon turn blue on the sender's WhatsApp for audio messages.

Mirrors the existing /message/markread structure (handler -> service
-> route + JID validation middleware). 'read' and 'played' are
distinct receipts in the WhatsApp protocol and can be sent
independently.
felipersd8 and others added 26 commits May 3, 2026 21:11
Three whatsmeow events were silently dropped by the dispatcher
("Unhandled event" log) because the central switch in
pkg/whatsmeow/service/whatsmeow.go had no case for them, even though
the underlying go.mau.fi/whatsmeow library emits them:

- *events.Picture (types/events/events.go) — fires when any user's
  profile picture or any group's photo changes. Carries JID, Author,
  Timestamp, Remove, PictureID. Useful for keeping CRM avatar caches
  in sync without polling /chat/fetchProfilePictureUrl per contact.
- *events.UserAbout (types/events/events.go) — fires when any user's
  "about" / status text changes. Carries JID, Status, Timestamp.
- *events.BusinessName (types/events/appstate.go) — fires lazily on
  inbound messages whose verified business name differs from the
  cached one. Carries JID, OldBusinessName, NewBusinessName.

Changes:

1. pkg/whatsmeow/service/whatsmeow.go — added three new cases to the
   myEventHandler dispatch switch, mirroring the minimal style of the
   existing *events.Contact / *events.PushName handlers (set doWebhook
   and postMap["event"]; the raw event is already attached at line 850
   via postMap["data"] = rawEvt).

2. pkg/whatsmeow/service/whatsmeow.go (CallWebhook) — added three new
   case branches on eventType so consumers can subscribe to PICTURE /
   USER_ABOUT / BUSINESS_NAME independently. Following the existing
   per-category split (e.g. MESSAGE vs SEND_MESSAGE vs READ_RECEIPT)
   rather than folding into CONTACT.

3. pkg/internal/event_types/event_types.go — added the three new
   subscription category constants to the const block, AllEventTypes
   slice, and validEventTypes map so IsEventType accepts them.

The change is purely additive: existing subscribers keep receiving
exactly what they did before. Consumers wanting the new events must
opt in by including PICTURE / USER_ABOUT / BUSINESS_NAME in their
subscribe list (or use ALL).

No new dependencies, no behavior changes to existing events. Build
verified with `go build ./...` against the pinned whatsmeow-lib
submodule (0923702fb3fac8525241f15331b92116485d69eb).
Add POST /group/settings endpoint to allow updating WhatsApp group
settings via the REST API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tingsStruct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fall back to PORT env var (injected by Railway) when SERVER_PORT is
not set, with final fallback to 8080.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Railway does not initialize git submodules on clone. Replace COPY of
the empty submodule dir with a direct git clone at the pinned commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	LICENSE
#	NOTICE
#	README.md
#	TRADEMARKS.md
#	evolution-go
#	pkg/core/c0.go
# Conflicts:
#	.gitignore
#	pkg/sendMessage/service/send_service.go
# Conflicts:
#	docs/docs.go
#	docs/swagger.json
#	docs/swagger.yaml
#	pkg/message/handler/message_handler.go
#	pkg/poll/handler/poll_handler.go
#	pkg/sendMessage/handler/send_handler.go
#	pkg/sendMessage/service/send_service.go
# Conflicts:
#	.gitignore
#	Dockerfile

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @felipersd8, your pull request is larger than the review limit of 150000 diff characters

@sourcery-ai

sourcery-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR modernizes the API documentation and response models, adds PIX payment and advanced interactive messaging support, refines presence and instance status behavior, introduces Prometheus metrics and a new manager dashboard, and extends event, group, message, and storage handling for better observability and integration.

Sequence diagram for PIX payment message sending flow

sequenceDiagram
  participant Client
  participant SendHandler as SendHandler
  participant SendService as sendService
  participant WAClient as MyClient.WAClient

  Client->>SendHandler: SendPix
  SendHandler->>SendService: SendPix
  SendService->>SendService: ensureClientConnected
  SendService->>SendService: json.Marshal(pixPaymentPayload)
  SendService->>SendService: SendMessage
  SendService->>WAClient: SendMessage
  WAClient-->>SendService: response
  SendService-->>SendHandler: MessageSendStruct
  SendHandler-->>Client: HTTP 200 (SendMessageResponse)
Loading

File-Level Changes

Change Details Files
Modernize Swagger/OpenAPI docs and response schemas to use explicit typed models instead of gin.H, and align paths and descriptions with current behavior.
  • Update docs/docs.go docTemplate to replace gin.H response schemas with concrete core and pkg_core models, add proper HTTP status descriptions and error models, and adjust several paths (e.g. /chat/history-sync) and summaries.
  • Regenerate docs/swagger.yaml and docs/swagger.json definitions to include new core response types, error envelopes, and detailed models for instances, groups, labels, polls, newsletters, license endpoints, and server status.
  • Add github_com_EvolutionAPI_evolution-go_pkg_core swagger_models.go with strongly-typed response structs for success and error cases across modules.
docs/docs.go
docs/swagger.yaml
docs/swagger.json
pkg/core/swagger_models.go
Extend send message capabilities with PIX payment interactive messages, richer button/carousel payloads, and updated list and button behavior routed through a central SendMessage pipeline.
  • Add PixStruct and pix* internal types plus SendPix implementation in send_service.go to construct native_flow payment_info interactive messages with additional biz nodes and support for merchant name, key, keyType, and items.
  • Enhance ButtonStruct/Button handling to support review_and_pay buttons, extra fields (imageUrl, videoUrl, catalogId, items), stricter validation, and richer JSON params for reply/copy/url/call types; route ButtonsMessage and InteractiveMessage through SendMessage with AdditionalNodes.
  • Rewrite SendList to use DocumentWithCaptionMessage-wrapped ListMessage with biz nodes instead of the legacy plain ListMessage format, and adjust sectionsToString/list row serialization accordingly.
  • Refine SendCarousel to build native_flow cards with optional image/video headers, JSON button params, and proper carousel card type and message version handling.
pkg/sendMessage/service/send_service.go
pkg/sendMessage/handler/send_handler.go
pkg/routes/routes.go
Improve WhatsApp presence handling, message persistence, and event emission including new picture/profile events and referral metadata extraction.
  • Refactor presence management in whatsmeow.go: introduce PresenceSender and handlePresenceTick, schedulePresenceUpdates using AlwaysOnline flag, remove random hour jitter and earlier presenceUnavailable behavior, and hook into advanced settings updates to start/stop presence goroutines.
  • Add persistMessageAsync helper and use it in myEventHandler for message delivery and poll events, storing referral JSON from contextInfo.externalAdReply when present into message_model.Message.Referral.
  • Extend event handling to emit Picture, UserAbout, and BusinessName events and route them through CallWebhook when subscribed, plus enrich ButtonClick payloads.
  • Add referral extraction helpers in referral.go and corresponding tests plus presence tests in whatsmeow_presence_test.go and message_repository tests for referral merge semantics.
  • Extend MessageService and handler with MarkPlayed endpoint to mark audio messages as played using ReceiptTypePlayed.
pkg/whatsmeow/service/whatsmeow.go
pkg/whatsmeow/service/referral.go
pkg/whatsmeow/service/whatsmeow_presence_test.go
pkg/message/model/message_model.go
pkg/message/repository/message_repository.go
pkg/message/repository/message_repository_test.go
pkg/message/service/message_service.go
pkg/message/handler/message_handler.go
docs/wiki/recursos-avancados/events-system.md
pkg/internal/event_types/event_types.go
Add Prometheus metrics, improved instance status reporting, and a new HTML dashboard plus deployment artifacts to support observability.
  • Introduce pkg/metrics with Prometheus registry, HTTP handler, Gin middleware, and a custom instanceCollector that exposes total/connected/disconnected instance counts, and wire it in main.go with /metrics endpoint and middleware.
  • Simplify instance Status logic to handle nil clients gracefully and avoid forcing connection, returning connected/loggedIn flags based on clientPointer.
  • Add manager/dashboard/index.html as a Tailwind-based dashboard that calls /instance/all and /server/ok using apikey from localStorage, and adjust routes so /manager serves the dashboard and /manager/*any serves the existing React app.
  • Add go module dependencies for Prometheus and sqlite driver, bump gorm versions, and add integration tests and helper in MinIO storage to ensure bucket creation via ensureBucketExists.
  • Add Dockerfile changes to clone whatsmeow-lib via git, include dashboard in image; add Railway config and example docker stack file for deployment.
pkg/metrics/metrics.go
cmd/evolution-go/main.go
pkg/instance/service/instance_service.go
manager/dashboard/index.html
pkg/storage/minio/media_storage.go
pkg/storage/minio/media_storage_test.go
pkg/storage/minio/media_storage_integration_test.go
go.mod
go.sum
Dockerfile
railway.toml
docker/stack-evocrm.yml
Extend group, chat, label, newsletter, poll, server, and license handlers and routes to use new core response models and support new operations like group settings and server status.
  • Update group_handler.go and group_service.go to return typed responses, fix GetMyGroups to filter by owner/participant phone numbers with LID-aware logic, and add UpdateGroupSettings handler wired via /group/settings using UpdateGroupSettingsStruct.
  • Switch chat_handler.go, message_handler.go, user_handler.go, label_handler.go, newsletter_handler.go, community_handler.go, poll_handler.go, server_handler.go, and instance_handler.go to emit core.* responses and errors instead of gin.H, update Swagger annotations accordingly, and expose /server/ok.
  • Adjust routes in routes.go to add new endpoints (/send/pix, /message/markplayed, /group/settings) and remove TODO comments from chat routes, aligning them with implemented handlers.
  • Add instance_repository.ErrInstanceNotFound and GetAllInstances plus tests, used by presence and metrics code to differentiate transient vs permanent missing instances.
  • Introduce swagger models for all these responses in pkg/core/swagger_models.go and wire via docs templates.
pkg/group/handler/group_handler.go
pkg/group/service/group_service.go
pkg/chat/handler/chat_handler.go
pkg/message/handler/message_handler.go
pkg/user/handler/user_handler.go
pkg/label/handler/label_handler.go
pkg/newsletter/handler/newsletter_handler.go
pkg/community/handler/community_handler.go
pkg/poll/handler/poll_handler.go
pkg/server/handler/server_handler.go
pkg/instance/handler/instance_handler.go
pkg/instance/repository/instance_repository.go
pkg/routes/routes.go
pkg/core/swagger_models.go
docs/docs.go
docs/swagger.yaml
docs/swagger.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants